On Mon, Jan 23, 2012 at 12:57 PM, Stéphane Ducasse < stephane.ducasse@inria.fr> wrote:
Hi elliot
What are these tryNamedPrimitive?
The methods in ProtoObject implement invoking primitives in the context of the debugger. The debugger used to use methods in Object, tryPrimitive0, tryPrimitive1: tryPrimitive2:with: et al, to invoke normal primitives. The debugger would choose the tryPrimitive1: method with the right number of arguments, modify its header to change its primitive number of the desired primitive, and then call it to invoke the primitive. This is not reentrant. If one is trying to debug the debugger then the tryPrimitive: method can be modified by the debugger that is debugging the debugger that is in the process of editing the tryPrimitive: method. To fix this, and eliminate the tryPrimitiveN methods, someone introduced the tryPrimitive:withArgs: primitive. (Note that in VisualWorks we solved this by creating a method on the fly to invoke a given primitive and evaluating it using a primitive that did the same thing as withArgs:executeMethod:).
Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
Why do we need them in ProtoObject?
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
Because I'm not sure that adding primitive to VM is always a good solution.
Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:.
HTH Eliot
Stef
On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck <
marianopeck@gmail.com> wrote:
Hi guys. I usually like to take a look to ProtoObject and see what is
really needed for the minimal object. But having 30% of the methods being #tryNamedPrimitive:with: * is not fun.
So...I wonder, do you think there could be another way so that to avoid
having all those methods in ProtoObject ?
Yes there is. I implemented primitive 218 in Cog,
primitiveDoNamedPrimitiveWithArgs, which is accessed via
tryNamedPrimitiveIn: aCompiledMethod for: aReceiver
withArgs: arguments
| selector theMethod spec receiverClass | <primitive: 218 error: ec> ec ifNotNil: ["If ec is an integer other than -1 there
was a problem with primitive 218,
not with the external primitive itself.
-1 indicates a generic failure (where
ec should be nil) but ec = nil means
primitive 218 is not implemented. So
interpret -1 to mean the external
primitive failed with a nil error code."
ec isInteger ifTrue: [ec = -1 ifTrue: [ec := nil] ifFalse: [self
primitiveFailed]].
^{PrimitiveFailToken. ec}]. "Assume a nil error code implies the primitive is
not implemented and fall back on the old code."
"Hack. Attempt to execute the named primitive from
the given compiled method"
arguments size > 8 ifTrue: [^{PrimitiveFailToken. nil}]. selector := #( tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1.
receiverClass := self objectClass: aReceiver. theMethod := receiverClass lookupSelector:
selector.
theMethod == nil ifTrue: [^{PrimitiveFailToken. nil}]. spec := theMethod literalAt: 1. spec replaceFrom: 1 to: spec size with:
(aCompiledMethod literalAt: 1) startingAt: 1.
Smalltalk unbindExternalPrimitives. ^self object: aReceiver perform: selector
withArguments: arguments inClass: receiverClass
(cf tryPrimitive: withArgs:) and used in
doPrimitive: primitiveIndex method: meth receiver:
receiver args: arguments
"Simulate a primitive method whose index is
primitiveIndex. The simulated receiver
and arguments are given as arguments to this
message. Any primitive which provokes
execution needs to be intercepted and simulated
to avoid execution running away."
| value | "If successful, push result and return resuming
context, else ^ { PrimitiveFailToken. errorCode }"
(primitiveIndex = 19) ifTrue: [ToolSet debugContext: self label:'Code simulation error' contents: nil]. "ContextPart>>blockCopy:; simulated to get startpc
right"
(primitiveIndex = 80 and: [(self objectClass:
receiver) includesBehavior: ContextPart])
ifTrue: [^self push: ((BlockContext
newForMethod: receiver method)
home:
receiver home
startpc:
pc + 2
nargs:
(arguments at: 1))].
(primitiveIndex = 81 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>value[:value:...]"
ifTrue: [^receiver pushArgs: arguments
from: self].
(primitiveIndex = 82 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>valueWithArguments:"
ifTrue: [^receiver pushArgs: arguments
first from: self].
primitiveIndex = 83 "afr 9/11/1998 19:50"
"Object>>perform:[with:...]"
ifTrue: [^self send: arguments first to: receiver with: arguments
allButFirst
super: false]. primitiveIndex = 84 "afr 9/11/1998 19:50 & eem
8/18/2009 17:04" "Object>>perform:withArguments:"
ifTrue: [^self send: arguments first to: receiver with: (arguments
at: 2)
startClass: nil]. primitiveIndex = 100 "eem 8/18/2009 16:57"
"Object>>perform:withArguments:inSuperclass:"
ifTrue: [^self send: arguments first to: receiver with: (arguments
at: 2)
startClass:
(arguments at: 3)].
"Mutex>>primitiveEnterCriticalSection
Mutex>>primitiveTestAndSetOwnershipOfCriticalSection"
(primitiveIndex = 186 or: [primitiveIndex = 187])
ifTrue:
[| active effective | active := Processor activeProcess. effective := active effectiveProcess. "active == effective" value := primitiveIndex = 186 ifTrue: [receiver
primitiveEnterCriticalSectionOnBehalfOf: effective]
ifFalse: [receiver
primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: effective].
^(value isArray and: [value size = 2 and: [value first ==
PrimitiveFailToken]])
ifTrue: [value] ifFalse: [self push: value]]. primitiveIndex = 188 ifTrue: "eem 5/27/2008 11:10
Object>>withArgs:executeMethod:"
[^MethodContext sender: self receiver: receiver method: (arguments at: 2) arguments: (arguments at: 1)]. "Closure primitives" (primitiveIndex = 200 and: [self == receiver])
ifTrue:
"ContextPart>>closureCopy:copiedValues:;
simulated to get startpc right"
[^self push: (BlockClosure
outerContext: receiver
startpc:
pc + 2
numArgs:
arguments first
copiedValues: arguments last)].
((primitiveIndex between: 201 and: 205)
"BlockClosure>>value[:value:...]"
or: [primitiveIndex between: 221 and: 222])
ifTrue: "BlockClosure>>valueNoContextSwitch[:]"
[^receiver simulateValueWithArguments:
arguments caller: self].
primitiveIndex = 206 ifTrue:
"BlockClosure>>valueWithArguments:"
[^receiver simulateValueWithArguments:
arguments first caller: self].
primitiveIndex = 118 ifTrue:
"tryPrimitive:withArgs:; avoid recursing in the VM"
[(arguments size = 2 and: [arguments first isInteger and: [arguments last class == Array]])
ifFalse:
[^ContextPart
primitiveFailTokenFor: nil].
^self doPrimitive: arguments first
method: meth receiver: receiver args: arguments last].
value := primitiveIndex = 120 "FFI method" ifTrue: [(meth literalAt:
- tryInvokeWithArguments: arguments]
ifFalse: [primitiveIndex =
117 "named primitives"
ifTrue:
[self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments]
ifFalse:
[receiver tryPrimitive: primitiveIndex withArgs: arguments]].
^(value isArray and: [value size = 2 and: [value first == PrimitiveFailToken]]) ifTrue: [value] ifFalse: [self push: value]
(find attached). But these need implementing in the standard VM before
they can be used in Pharo, Squeak, etc.
Thanks
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
On Mon, Jan 23, 2012 at 10:22 PM, Eliot Miranda eliot.miranda@gmail.comwrote:
On Mon, Jan 23, 2012 at 12:57 PM, Stéphane Ducasse < stephane.ducasse@inria.fr> wrote:
Hi elliot
What are these tryNamedPrimitive?
The methods in ProtoObject implement invoking primitives in the context of the debugger.
Ok, I was not that far :)
The debugger used to use methods in Object, tryPrimitive0, tryPrimitive1: tryPrimitive2:with: et al, to invoke normal primitives. The debugger would choose the tryPrimitive1: method with the right number of arguments, modify its header to change its primitive number of the desired primitive, and then call it to invoke the primitive. This is not reentrant. If one is trying to debug the debugger then the tryPrimitive: method can be modified by the debugger that is debugging the debugger that is in the process of editing the tryPrimitive: method. To fix this, and eliminate the tryPrimitiveN methods, someone introduced the tryPrimitive:withArgs: primitive. (Note that in VisualWorks we solved this by creating a method on the fly to invoke a given primitive and evaluating it using a primitive that did the same thing as withArgs:executeMethod:).
Eliot, isn't that what I have suggested in my previous email? at least as I can understand, that was my idea. In fact, since even if we have this new primitive, we have to keep those methods because Pharo could be opened in older VMs, wouldn't it make sense such proposal? because that way we can remove those methods. So...if we are in latest cog, we use the primitive, otherwise, we generate the CM on the fly and we execute it. In both cases, we do not use these methods.
Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
What what about temporally (to be removed in the future) just when we are using older VMs?
Why do we need them in ProtoObject?
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
Because I'm not sure that adding primitive to VM is always a good solution.
Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:.
HTH Eliot
Stef
On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck <
marianopeck@gmail.com> wrote:
Hi guys. I usually like to take a look to ProtoObject and see what is
really needed for the minimal object. But having 30% of the methods being #tryNamedPrimitive:with: * is not fun.
So...I wonder, do you think there could be another way so that to avoid
having all those methods in ProtoObject ?
Yes there is. I implemented primitive 218 in Cog,
primitiveDoNamedPrimitiveWithArgs, which is accessed via
tryNamedPrimitiveIn: aCompiledMethod for: aReceiver
withArgs: arguments
| selector theMethod spec receiverClass | <primitive: 218 error: ec> ec ifNotNil: ["If ec is an integer other than -1 there
was a problem with primitive 218,
not with the external primitive itself.
-1 indicates a generic failure (where
ec should be nil) but ec = nil means
primitive 218 is not implemented. So
interpret -1 to mean the external
primitive failed with a nil error code."
ec isInteger ifTrue: [ec = -1 ifTrue: [ec := nil] ifFalse: [self
primitiveFailed]].
^{PrimitiveFailToken. ec}]. "Assume a nil error code implies the primitive is
not implemented and fall back on the old code."
"Hack. Attempt to execute the named primitive
from the given compiled method"
arguments size > 8 ifTrue: [^{PrimitiveFailToken. nil}]. selector := #( tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1.
receiverClass := self objectClass: aReceiver. theMethod := receiverClass lookupSelector:
selector.
theMethod == nil ifTrue: [^{PrimitiveFailToken. nil}]. spec := theMethod literalAt: 1. spec replaceFrom: 1 to: spec size with:
(aCompiledMethod literalAt: 1) startingAt: 1.
Smalltalk unbindExternalPrimitives. ^self object: aReceiver perform: selector
withArguments: arguments inClass: receiverClass
(cf tryPrimitive: withArgs:) and used in
doPrimitive: primitiveIndex method: meth receiver:
receiver args: arguments
"Simulate a primitive method whose index is
primitiveIndex. The simulated receiver
and arguments are given as arguments to this
message. Any primitive which provokes
execution needs to be intercepted and simulated
to avoid execution running away."
| value | "If successful, push result and return resuming
context, else ^ { PrimitiveFailToken. errorCode }"
(primitiveIndex = 19) ifTrue: [ToolSet debugContext: self label:'Code simulation error' contents: nil]. "ContextPart>>blockCopy:; simulated to get
startpc right"
(primitiveIndex = 80 and: [(self objectClass:
receiver) includesBehavior: ContextPart])
ifTrue: [^self push: ((BlockContext
newForMethod: receiver method)
home:
receiver home
startpc:
pc + 2
nargs:
(arguments at: 1))].
(primitiveIndex = 81 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>value[:value:...]"
ifTrue: [^receiver pushArgs: arguments
from: self].
(primitiveIndex = 82 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>valueWithArguments:"
ifTrue: [^receiver pushArgs: arguments
first from: self].
primitiveIndex = 83 "afr 9/11/1998 19:50"
"Object>>perform:[with:...]"
ifTrue: [^self send: arguments first to: receiver with: arguments
allButFirst
super: false]. primitiveIndex = 84 "afr 9/11/1998 19:50 & eem
8/18/2009 17:04" "Object>>perform:withArguments:"
ifTrue: [^self send: arguments first to: receiver with: (arguments
at: 2)
startClass: nil]. primitiveIndex = 100 "eem 8/18/2009 16:57"
"Object>>perform:withArguments:inSuperclass:"
ifTrue: [^self send: arguments first to: receiver with: (arguments
at: 2)
startClass:
(arguments at: 3)].
"Mutex>>primitiveEnterCriticalSection
Mutex>>primitiveTestAndSetOwnershipOfCriticalSection"
(primitiveIndex = 186 or: [primitiveIndex = 187])
ifTrue:
[| active effective | active := Processor activeProcess. effective := active effectiveProcess. "active == effective" value := primitiveIndex = 186 ifTrue: [receiver
primitiveEnterCriticalSectionOnBehalfOf: effective]
ifFalse:
[receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: effective].
^(value isArray and: [value size = 2 and: [value first ==
PrimitiveFailToken]])
ifTrue: [value] ifFalse: [self push: value]]. primitiveIndex = 188 ifTrue: "eem 5/27/2008 11:10
Object>>withArgs:executeMethod:"
[^MethodContext sender: self receiver: receiver method: (arguments at: 2) arguments: (arguments at: 1)]. "Closure primitives" (primitiveIndex = 200 and: [self == receiver])
ifTrue:
"ContextPart>>closureCopy:copiedValues:;
simulated to get startpc right"
[^self push: (BlockClosure
outerContext: receiver
startpc:
pc + 2
numArgs:
arguments first
copiedValues: arguments last)].
((primitiveIndex between: 201 and: 205)
"BlockClosure>>value[:value:...]"
or: [primitiveIndex between: 221 and: 222])
ifTrue: "BlockClosure>>valueNoContextSwitch[:]"
[^receiver simulateValueWithArguments:
arguments caller: self].
primitiveIndex = 206 ifTrue:
"BlockClosure>>valueWithArguments:"
[^receiver simulateValueWithArguments:
arguments first caller: self].
primitiveIndex = 118 ifTrue:
"tryPrimitive:withArgs:; avoid recursing in the VM"
[(arguments size = 2 and: [arguments first isInteger and: [arguments last class == Array]])
ifFalse:
[^ContextPart
primitiveFailTokenFor: nil].
^self doPrimitive: arguments first
method: meth receiver: receiver args: arguments last].
value := primitiveIndex = 120 "FFI method" ifTrue: [(meth literalAt:
- tryInvokeWithArguments: arguments]
ifFalse: [primitiveIndex =
117 "named primitives"
ifTrue:
[self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments]
ifFalse:
[receiver tryPrimitive: primitiveIndex withArgs: arguments]].
^(value isArray and: [value size = 2 and: [value first == PrimitiveFailToken]]) ifTrue: [value] ifFalse: [self push: value]
(find attached). But these need implementing in the standard VM before
they can be used in Pharo, Squeak, etc.
Thanks
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- best, Eliot
On Tue, Jan 24, 2012 at 12:41 AM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
On Mon, Jan 23, 2012 at 10:22 PM, Eliot Miranda eliot.miranda@gmail.comwrote:
On Mon, Jan 23, 2012 at 12:57 PM, Stéphane Ducasse < stephane.ducasse@inria.fr> wrote:
Hi elliot
What are these tryNamedPrimitive?
The methods in ProtoObject implement invoking primitives in the context of the debugger.
Ok, I was not that far :)
The debugger used to use methods in Object, tryPrimitive0, tryPrimitive1: tryPrimitive2:with: et al, to invoke normal primitives. The debugger would choose the tryPrimitive1: method with the right number of arguments, modify its header to change its primitive number of the desired primitive, and then call it to invoke the primitive. This is not reentrant. If one is trying to debug the debugger then the tryPrimitive: method can be modified by the debugger that is debugging the debugger that is in the process of editing the tryPrimitive: method. To fix this, and eliminate the tryPrimitiveN methods, someone introduced the tryPrimitive:withArgs: primitive. (Note that in VisualWorks we solved this by creating a method on the fly to invoke a given primitive and evaluating it using a primitive that did the same thing as withArgs:executeMethod:).
Eliot, isn't that what I have suggested in my previous email? at least as I can understand, that was my idea. In fact, since even if we have this new primitive, we have to keep those methods because Pharo could be opened in older VMs, wouldn't it make sense such proposal? because that way we can remove those methods. So...if we are in latest cog, we use the primitive, otherwise, we generate the CM on the fly and we execute it. In both cases, we do not use these methods.
Sure.
Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
What what about temporally (to be removed in the future) just when we are using older VMs?
If temporary, then fine. But its all work :)
Why do we need them in ProtoObject?
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
Because I'm not sure that adding primitive to VM is always a good solution.
Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:.
HTH Eliot
Stef
On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck <
marianopeck@gmail.com> wrote:
Hi guys. I usually like to take a look to ProtoObject and see what is
really needed for the minimal object. But having 30% of the methods being #tryNamedPrimitive:with: * is not fun.
So...I wonder, do you think there could be another way so that to
avoid having all those methods in ProtoObject ?
Yes there is. I implemented primitive 218 in Cog,
primitiveDoNamedPrimitiveWithArgs, which is accessed via
tryNamedPrimitiveIn: aCompiledMethod for: aReceiver
withArgs: arguments
| selector theMethod spec receiverClass | <primitive: 218 error: ec> ec ifNotNil: ["If ec is an integer other than -1
there was a problem with primitive 218,
not with the external primitive
itself. -1 indicates a generic failure (where
ec should be nil) but ec = nil means
primitive 218 is not implemented. So
interpret -1 to mean the external
primitive failed with a nil error code."
ec isInteger ifTrue: [ec = -1 ifTrue: [ec := nil] ifFalse: [self
primitiveFailed]].
^{PrimitiveFailToken. ec}]. "Assume a nil error code implies the primitive
is not implemented and fall back on the old code."
"Hack. Attempt to execute the named primitive
from the given compiled method"
arguments size > 8 ifTrue: [^{PrimitiveFailToken. nil}]. selector := #( tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1.
receiverClass := self objectClass: aReceiver. theMethod := receiverClass lookupSelector:
selector.
theMethod == nil ifTrue: [^{PrimitiveFailToken. nil}]. spec := theMethod literalAt: 1. spec replaceFrom: 1 to: spec size with:
(aCompiledMethod literalAt: 1) startingAt: 1.
Smalltalk unbindExternalPrimitives. ^self object: aReceiver perform: selector
withArguments: arguments inClass: receiverClass
(cf tryPrimitive: withArgs:) and used in
doPrimitive: primitiveIndex method: meth receiver:
receiver args: arguments
"Simulate a primitive method whose index is
primitiveIndex. The simulated receiver
and arguments are given as arguments to this
message. Any primitive which provokes
execution needs to be intercepted and simulated
to avoid execution running away."
| value | "If successful, push result and return resuming
context, else ^ { PrimitiveFailToken. errorCode }"
(primitiveIndex = 19) ifTrue: [ToolSet debugContext: self label:'Code simulation error' contents: nil]. "ContextPart>>blockCopy:; simulated to get
startpc right"
(primitiveIndex = 80 and: [(self objectClass:
receiver) includesBehavior: ContextPart])
ifTrue: [^self push: ((BlockContext
newForMethod: receiver method)
home:
receiver home
startpc:
pc + 2
nargs:
(arguments at: 1))].
(primitiveIndex = 81 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>value[:value:...]"
ifTrue: [^receiver pushArgs: arguments
from: self].
(primitiveIndex = 82 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>valueWithArguments:"
ifTrue: [^receiver pushArgs: arguments
first from: self].
primitiveIndex = 83 "afr 9/11/1998 19:50"
"Object>>perform:[with:...]"
ifTrue: [^self send: arguments first to: receiver with: arguments
allButFirst
super: false]. primitiveIndex = 84 "afr 9/11/1998 19:50 & eem
8/18/2009 17:04" "Object>>perform:withArguments:"
ifTrue: [^self send: arguments first to: receiver with: (arguments
at: 2)
startClass: nil]. primitiveIndex = 100 "eem 8/18/2009 16:57"
"Object>>perform:withArguments:inSuperclass:"
ifTrue: [^self send: arguments first to: receiver with: (arguments
at: 2)
startClass:
(arguments at: 3)].
"Mutex>>primitiveEnterCriticalSection
Mutex>>primitiveTestAndSetOwnershipOfCriticalSection"
(primitiveIndex = 186 or: [primitiveIndex =
187]) ifTrue:
[| active effective | active := Processor activeProcess. effective := active effectiveProcess. "active == effective" value := primitiveIndex = 186 ifTrue:
[receiver primitiveEnterCriticalSectionOnBehalfOf: effective]
ifFalse:
[receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: effective].
^(value isArray and: [value size = 2 and: [value first ==
PrimitiveFailToken]])
ifTrue: [value] ifFalse: [self push: value]]. primitiveIndex = 188 ifTrue: "eem 5/27/2008
11:10 Object>>withArgs:executeMethod:"
[^MethodContext sender: self receiver: receiver method: (arguments at: 2) arguments: (arguments at: 1)]. "Closure primitives" (primitiveIndex = 200 and: [self == receiver])
ifTrue:
"ContextPart>>closureCopy:copiedValues:;
simulated to get startpc right"
[^self push: (BlockClosure
outerContext: receiver
startpc:
pc + 2
numArgs:
arguments first
copiedValues: arguments last)].
((primitiveIndex between: 201 and: 205)
"BlockClosure>>value[:value:...]"
or: [primitiveIndex between: 221 and: 222])
ifTrue: "BlockClosure>>valueNoContextSwitch[:]"
[^receiver simulateValueWithArguments:
arguments caller: self].
primitiveIndex = 206 ifTrue:
"BlockClosure>>valueWithArguments:"
[^receiver simulateValueWithArguments:
arguments first caller: self].
primitiveIndex = 118 ifTrue:
"tryPrimitive:withArgs:; avoid recursing in the VM"
[(arguments size = 2 and: [arguments first isInteger and: [arguments last class == Array]])
ifFalse:
[^ContextPart
primitiveFailTokenFor: nil].
^self doPrimitive: arguments first
method: meth receiver: receiver args: arguments last].
value := primitiveIndex = 120 "FFI method" ifTrue: [(meth
literalAt: 1) tryInvokeWithArguments: arguments]
ifFalse: [primitiveIndex
= 117 "named primitives"
ifTrue:
[self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments]
ifFalse:
[receiver tryPrimitive: primitiveIndex withArgs: arguments]].
^(value isArray and: [value size = 2 and: [value first == PrimitiveFailToken]]) ifTrue: [value] ifFalse: [self push: value]
(find attached). But these need implementing in the standard VM
before they can be used in Pharo, Squeak, etc.
Thanks
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
What what about temporally (to be removed in the future) just when we are using older VMs?
If temporary, then fine. But its all work :)
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
Why do we need them in ProtoObject?
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
Because I'm not sure that adding primitive to VM is always a good solution.
Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:.
HTH Eliot
Stef
On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck <
marianopeck@gmail.com> wrote:
Hi guys. I usually like to take a look to ProtoObject and see what is
really needed for the minimal object. But having 30% of the methods being #tryNamedPrimitive:with: * is not fun.
So...I wonder, do you think there could be another way so that to
avoid having all those methods in ProtoObject ?
Yes there is. I implemented primitive 218 in Cog,
primitiveDoNamedPrimitiveWithArgs, which is accessed via
tryNamedPrimitiveIn: aCompiledMethod for: aReceiver
withArgs: arguments
| selector theMethod spec receiverClass | <primitive: 218 error: ec> ec ifNotNil: ["If ec is an integer other than -1
there was a problem with primitive 218,
not with the external primitive
itself. -1 indicates a generic failure (where
ec should be nil) but ec = nil means
primitive 218 is not implemented. So
interpret -1 to mean the external
primitive failed with a nil error code."
ec isInteger ifTrue: [ec = -1 ifTrue: [ec := nil] ifFalse: [self
primitiveFailed]].
^{PrimitiveFailToken. ec}]. "Assume a nil error code implies the primitive
is not implemented and fall back on the old code."
"Hack. Attempt to execute the named primitive
from the given compiled method"
arguments size > 8 ifTrue: [^{PrimitiveFailToken. nil}]. selector := #( tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1.
receiverClass := self objectClass: aReceiver. theMethod := receiverClass lookupSelector:
selector.
theMethod == nil ifTrue: [^{PrimitiveFailToken. nil}]. spec := theMethod literalAt: 1. spec replaceFrom: 1 to: spec size with:
(aCompiledMethod literalAt: 1) startingAt: 1.
Smalltalk unbindExternalPrimitives. ^self object: aReceiver perform: selector
withArguments: arguments inClass: receiverClass
(cf tryPrimitive: withArgs:) and used in
doPrimitive: primitiveIndex method: meth receiver:
receiver args: arguments
"Simulate a primitive method whose index is
primitiveIndex. The simulated receiver
and arguments are given as arguments to this
message. Any primitive which provokes
execution needs to be intercepted and
simulated to avoid execution running away."
| value | "If successful, push result and return resuming
context, else ^ { PrimitiveFailToken. errorCode }"
(primitiveIndex = 19) ifTrue: [ToolSet debugContext: self label:'Code simulation error' contents: nil]. "ContextPart>>blockCopy:; simulated to get
startpc right"
(primitiveIndex = 80 and: [(self objectClass:
receiver) includesBehavior: ContextPart])
ifTrue: [^self push: ((BlockContext
newForMethod: receiver method)
home:
receiver home
startpc: pc + 2
nargs:
(arguments at: 1))].
(primitiveIndex = 81 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>value[:value:...]"
ifTrue: [^receiver pushArgs: arguments
from: self].
(primitiveIndex = 82 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>valueWithArguments:"
ifTrue: [^receiver pushArgs: arguments
first from: self].
primitiveIndex = 83 "afr 9/11/1998 19:50"
"Object>>perform:[with:...]"
ifTrue: [^self send: arguments first to: receiver with: arguments
allButFirst
super: false]. primitiveIndex = 84 "afr 9/11/1998 19:50 & eem
8/18/2009 17:04" "Object>>perform:withArguments:"
ifTrue: [^self send: arguments first to: receiver with:
(arguments at: 2)
startClass:
nil].
primitiveIndex = 100 "eem 8/18/2009 16:57"
"Object>>perform:withArguments:inSuperclass:"
ifTrue: [^self send: arguments first to: receiver with:
(arguments at: 2)
startClass:
(arguments at: 3)].
"Mutex>>primitiveEnterCriticalSection
Mutex>>primitiveTestAndSetOwnershipOfCriticalSection"
(primitiveIndex = 186 or: [primitiveIndex =
187]) ifTrue:
[| active effective | active := Processor activeProcess. effective := active effectiveProcess. "active == effective" value := primitiveIndex = 186 ifTrue:
[receiver primitiveEnterCriticalSectionOnBehalfOf: effective]
ifFalse:
[receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: effective].
^(value isArray and: [value size = 2 and: [value first ==
PrimitiveFailToken]])
ifTrue: [value] ifFalse: [self push: value]]. primitiveIndex = 188 ifTrue: "eem 5/27/2008
11:10 Object>>withArgs:executeMethod:"
[^MethodContext sender: self receiver: receiver method: (arguments at: 2) arguments: (arguments at: 1)]. "Closure primitives" (primitiveIndex = 200 and: [self == receiver])
ifTrue:
"ContextPart>>closureCopy:copiedValues:; simulated to get startpc right"
[^self push: (BlockClosure
outerContext: receiver
startpc: pc + 2
numArgs: arguments first
copiedValues: arguments last)].
((primitiveIndex between: 201 and: 205)
"BlockClosure>>value[:value:...]"
or: [primitiveIndex between: 221 and: 222])
ifTrue: "BlockClosure>>valueNoContextSwitch[:]"
[^receiver simulateValueWithArguments:
arguments caller: self].
primitiveIndex = 206 ifTrue:
"BlockClosure>>valueWithArguments:"
[^receiver simulateValueWithArguments:
arguments first caller: self].
primitiveIndex = 118 ifTrue:
"tryPrimitive:withArgs:; avoid recursing in the VM"
[(arguments size = 2 and: [arguments first isInteger and: [arguments last class == Array]])
ifFalse:
[^ContextPart
primitiveFailTokenFor: nil].
^self doPrimitive: arguments first
method: meth receiver: receiver args: arguments last].
value := primitiveIndex = 120 "FFI method" ifTrue: [(meth
literalAt: 1) tryInvokeWithArguments: arguments]
ifFalse: [primitiveIndex
= 117 "named primitives"
ifTrue:
[self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments]
ifFalse:
[receiver tryPrimitive: primitiveIndex withArgs: arguments]].
^(value isArray and: [value size = 2 and: [value first == PrimitiveFailToken]]) ifTrue: [value] ifFalse: [self push: value]
(find attached). But these need implementing in the standard VM
before they can be used in Pharo, Squeak, etc.
Thanks
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
sorry...here is the attached.
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
What what about temporally (to be removed in the future) just when we are using older VMs?
If temporary, then fine. But its all work :)
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
Why do we need them in ProtoObject?
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
Because I'm not sure that adding primitive to VM is always a good solution.
Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:.
HTH Eliot
Stef
On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck <
marianopeck@gmail.com> wrote:
Hi guys. I usually like to take a look to ProtoObject and see what
is really needed for the minimal object. But having 30% of the methods being #tryNamedPrimitive:with: * is not fun.
So...I wonder, do you think there could be another way so that to
avoid having all those methods in ProtoObject ?
Yes there is. I implemented primitive 218 in Cog,
primitiveDoNamedPrimitiveWithArgs, which is accessed via
tryNamedPrimitiveIn: aCompiledMethod for: aReceiver
withArgs: arguments
| selector theMethod spec receiverClass | <primitive: 218 error: ec> ec ifNotNil: ["If ec is an integer other than -1
there was a problem with primitive 218,
not with the external primitive
itself. -1 indicates a generic failure (where
ec should be nil) but ec = nil means
primitive 218 is not implemented. So
interpret -1 to mean the external
primitive failed with a nil error code."
ec isInteger ifTrue: [ec = -1 ifTrue: [ec := nil] ifFalse: [self
primitiveFailed]].
^{PrimitiveFailToken. ec}]. "Assume a nil error code implies the primitive
is not implemented and fall back on the old code."
"Hack. Attempt to execute the named primitive
from the given compiled method"
arguments size > 8 ifTrue: [^{PrimitiveFailToken. nil}]. selector := #( tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1.
receiverClass := self objectClass: aReceiver. theMethod := receiverClass lookupSelector:
selector.
theMethod == nil ifTrue: [^{PrimitiveFailToken. nil}]. spec := theMethod literalAt: 1. spec replaceFrom: 1 to: spec size with:
(aCompiledMethod literalAt: 1) startingAt: 1.
Smalltalk unbindExternalPrimitives. ^self object: aReceiver perform: selector
withArguments: arguments inClass: receiverClass
(cf tryPrimitive: withArgs:) and used in
doPrimitive: primitiveIndex method: meth receiver:
receiver args: arguments
"Simulate a primitive method whose index is
primitiveIndex. The simulated receiver
and arguments are given as arguments to this
message. Any primitive which provokes
execution needs to be intercepted and
simulated to avoid execution running away."
| value | "If successful, push result and return
resuming context, else ^ { PrimitiveFailToken. errorCode }"
(primitiveIndex = 19) ifTrue: [ToolSet debugContext: self label:'Code simulation error' contents: nil]. "ContextPart>>blockCopy:; simulated to get
startpc right"
(primitiveIndex = 80 and: [(self objectClass:
receiver) includesBehavior: ContextPart])
ifTrue: [^self push: ((BlockContext
newForMethod: receiver method)
home:
receiver home
startpc: pc + 2
nargs:
(arguments at: 1))].
(primitiveIndex = 81 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>value[:value:...]"
ifTrue: [^receiver pushArgs: arguments
from: self].
(primitiveIndex = 82 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>valueWithArguments:"
ifTrue: [^receiver pushArgs: arguments
first from: self].
primitiveIndex = 83 "afr 9/11/1998 19:50"
"Object>>perform:[with:...]"
ifTrue: [^self send: arguments first to: receiver with:
arguments allButFirst
super: false]. primitiveIndex = 84 "afr 9/11/1998 19:50 & eem
8/18/2009 17:04" "Object>>perform:withArguments:"
ifTrue: [^self send: arguments first to: receiver with:
(arguments at: 2)
startClass:
nil].
primitiveIndex = 100 "eem 8/18/2009 16:57"
"Object>>perform:withArguments:inSuperclass:"
ifTrue: [^self send: arguments first to: receiver with:
(arguments at: 2)
startClass:
(arguments at: 3)].
"Mutex>>primitiveEnterCriticalSection
Mutex>>primitiveTestAndSetOwnershipOfCriticalSection"
(primitiveIndex = 186 or: [primitiveIndex =
187]) ifTrue:
[| active effective | active := Processor activeProcess. effective := active effectiveProcess. "active == effective" value := primitiveIndex = 186 ifTrue:
[receiver primitiveEnterCriticalSectionOnBehalfOf: effective]
ifFalse:
[receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: effective].
^(value isArray and: [value size = 2 and: [value first ==
PrimitiveFailToken]])
ifTrue: [value] ifFalse: [self push: value]]. primitiveIndex = 188 ifTrue: "eem 5/27/2008
11:10 Object>>withArgs:executeMethod:"
[^MethodContext sender: self receiver: receiver method: (arguments at: 2) arguments: (arguments at: 1)]. "Closure primitives" (primitiveIndex = 200 and: [self == receiver])
ifTrue:
"ContextPart>>closureCopy:copiedValues:; simulated to get startpc right"
[^self push: (BlockClosure
outerContext: receiver
startpc: pc + 2
numArgs: arguments first
copiedValues: arguments last)].
((primitiveIndex between: 201 and: 205)
"BlockClosure>>value[:value:...]"
or: [primitiveIndex between: 221 and: 222])
ifTrue: "BlockClosure>>valueNoContextSwitch[:]"
[^receiver simulateValueWithArguments:
arguments caller: self].
primitiveIndex = 206 ifTrue:
"BlockClosure>>valueWithArguments:"
[^receiver simulateValueWithArguments:
arguments first caller: self].
primitiveIndex = 118 ifTrue:
"tryPrimitive:withArgs:; avoid recursing in the VM"
[(arguments size = 2 and: [arguments first isInteger and: [arguments last class ==
Array]]) ifFalse:
[^ContextPart
primitiveFailTokenFor: nil].
^self doPrimitive: arguments first
method: meth receiver: receiver args: arguments last].
value := primitiveIndex = 120 "FFI method" ifTrue: [(meth
literalAt: 1) tryInvokeWithArguments: arguments]
ifFalse:
[primitiveIndex = 117 "named primitives"
ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments]
ifFalse:
[receiver tryPrimitive: primitiveIndex withArgs: arguments]].
^(value isArray and: [value size = 2 and: [value first == PrimitiveFailToken]]) ifTrue: [value] ifFalse: [self push: value]
(find attached). But these need implementing in the standard VM
before they can be used in Pharo, Squeak, etc.
Thanks
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
On Tue, Jan 24, 2012 at 2:02 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
sorry...here is the attached.
OK. What you've done will work. Some criticism: Since the method is new the flushCache should be unneeded. I think either copying a template method (e.g. stored in a class variable) and changing its argument count, or creating a method directly (look at the generate: method) would be much faster. Speed can be important in the debugger.
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
What what about temporally (to be removed in the future) just when we are using older VMs?
If temporary, then fine. But its all work :)
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
Why do we need them in ProtoObject?
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
Because I'm not sure that adding primitive to VM is always a good solution.
Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:.
HTH Eliot
Stef
> On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck < marianopeck@gmail.com> wrote: > Hi guys. I usually like to take a look to ProtoObject and see what is really needed for the minimal object. But having 30% of the methods being #tryNamedPrimitive:with: * is not fun. > So...I wonder, do you think there could be another way so that to avoid having all those methods in ProtoObject ? > > Yes there is. I implemented primitive 218 in Cog, primitiveDoNamedPrimitiveWithArgs, which is accessed via > > > tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments > | selector theMethod spec receiverClass | > <primitive: 218 error: ec> > ec ifNotNil: > ["If ec is an integer other than -1 there was a problem with primitive 218, > not with the external primitive itself. -1 indicates a generic failure (where > ec should be nil) but ec = nil means primitive 218 is not implemented. So > interpret -1 to mean the external primitive failed with a nil error code." > ec isInteger ifTrue: > [ec = -1 > ifTrue: [ec := nil] > ifFalse: [self primitiveFailed]]. > ^{PrimitiveFailToken. ec}]. > "Assume a nil error code implies the primitive is not implemented and fall back on the old code." > "Hack. Attempt to execute the named primitive from the given compiled method" > arguments size > 8 ifTrue: > [^{PrimitiveFailToken. nil}]. > selector := #( > tryNamedPrimitive > tryNamedPrimitive: > tryNamedPrimitive:with: > tryNamedPrimitive:with:with: > tryNamedPrimitive:with:with:with: > tryNamedPrimitive:with:with:with:with: > tryNamedPrimitive:with:with:with:with:with: > tryNamedPrimitive:with:with:with:with:with:with: > tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. > receiverClass := self objectClass: aReceiver. > theMethod := receiverClass lookupSelector: selector. > theMethod == nil ifTrue: > [^{PrimitiveFailToken. nil}]. > spec := theMethod literalAt: 1. > spec replaceFrom: 1 to: spec size with: (aCompiledMethod literalAt: 1) startingAt: 1. > Smalltalk unbindExternalPrimitives. > ^self object: aReceiver perform: selector withArguments: arguments inClass: receiverClass > > (cf tryPrimitive: withArgs:) and used in > > > doPrimitive: primitiveIndex method: meth receiver: receiver args: arguments > "Simulate a primitive method whose index is primitiveIndex. The simulated receiver > and arguments are given as arguments to this message. Any primitive which provokes > execution needs to be intercepted and simulated to avoid execution running away." > > | value | > "If successful, push result and return resuming context, else ^ { PrimitiveFailToken. errorCode }" > (primitiveIndex = 19) ifTrue: > [ToolSet > debugContext: self > label:'Code simulation error' > contents: nil]. > > "ContextPart>>blockCopy:; simulated to get startpc right" > (primitiveIndex = 80 and: [(self objectClass: receiver) includesBehavior: ContextPart]) > ifTrue: [^self push: ((BlockContext newForMethod: receiver method) > home: receiver home > startpc: pc + 2 > nargs: (arguments at: 1))]. > (primitiveIndex = 81 and: [(self objectClass: receiver) == BlockContext]) "BlockContext>>value[:value:...]" > ifTrue: [^receiver pushArgs: arguments from: self]. > (primitiveIndex = 82 and: [(self objectClass: receiver) == BlockContext]) "BlockContext>>valueWithArguments:" > ifTrue: [^receiver pushArgs: arguments first from: self]. > primitiveIndex = 83 "afr 9/11/1998 19:50" "Object>>perform:[with:...]" > ifTrue: [^self send: arguments first > to: receiver > with: arguments allButFirst > super: false]. > primitiveIndex = 84 "afr 9/11/1998 19:50 & eem 8/18/2009 17:04" "Object>>perform:withArguments:" > ifTrue: [^self send: arguments first > to: receiver > with: (arguments at: 2) > startClass: nil]. > primitiveIndex = 100 "eem 8/18/2009 16:57" "Object>>perform:withArguments:inSuperclass:" > ifTrue: [^self send: arguments first > to: receiver > with: (arguments at: 2) > startClass: (arguments at: 3)]. > > "Mutex>>primitiveEnterCriticalSection > Mutex>>primitiveTestAndSetOwnershipOfCriticalSection" > (primitiveIndex = 186 or: [primitiveIndex = 187]) ifTrue: > [| active effective | > active := Processor activeProcess. > effective := active effectiveProcess. > "active == effective" > value := primitiveIndex = 186 > ifTrue: [receiver primitiveEnterCriticalSectionOnBehalfOf: effective] > ifFalse: [receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: effective]. > ^(value isArray > and: [value size = 2 > and: [value first == PrimitiveFailToken]]) > ifTrue: [value] > ifFalse: [self push: value]]. > > primitiveIndex = 188 ifTrue: "eem 5/27/2008 11:10 Object>>withArgs:executeMethod:" > [^MethodContext > sender: self > receiver: receiver > method: (arguments at: 2) > arguments: (arguments at: 1)]. > > "Closure primitives" > (primitiveIndex = 200 and: [self == receiver]) ifTrue: > "ContextPart>>closureCopy:copiedValues:; simulated to get startpc right" > [^self push: (BlockClosure > outerContext: receiver > startpc: pc + 2 > numArgs: arguments first > copiedValues: arguments last)]. > ((primitiveIndex between: 201 and: 205) "BlockClosure>>value[:value:...]" > or: [primitiveIndex between: 221 and: 222]) ifTrue: "BlockClosure>>valueNoContextSwitch[:]" > [^receiver simulateValueWithArguments: arguments caller: self]. > primitiveIndex = 206 ifTrue: "BlockClosure>>valueWithArguments:" > [^receiver simulateValueWithArguments: arguments first caller: self]. > > primitiveIndex = 118 ifTrue: "tryPrimitive:withArgs:; avoid recursing in the VM" > [(arguments size = 2 > and: [arguments first isInteger > and: [arguments last class == Array]]) ifFalse: > [^ContextPart primitiveFailTokenFor: nil]. > ^self doPrimitive: arguments first method: meth receiver: receiver args: arguments last]. > > value := primitiveIndex = 120 "FFI method" > ifTrue: [(meth literalAt: 1) tryInvokeWithArguments: arguments] > ifFalse: > [primitiveIndex = 117 "named primitives" > ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] > ifFalse: > [receiver tryPrimitive: primitiveIndex withArgs: arguments]]. > ^(value isArray > and: [value size = 2 > and: [value first == PrimitiveFailToken]]) > ifTrue: [value] > ifFalse: [self push: value] > > (find attached). But these need implementing in the standard VM before they can be used in Pharo, Squeak, etc. > > > Thanks > > -- > Mariano > http://marianopeck.wordpress.com > > > > > -- > best, > Eliot >
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
On Wed, Jan 25, 2012 at 2:43 AM, Eliot Miranda eliot.miranda@gmail.comwrote:
On Tue, Jan 24, 2012 at 2:02 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
sorry...here is the attached.
OK. What you've done will work. Some criticism: Since the method is new the flushCache should be unneeded.
True.
I think either copying a template method (e.g. stored in a class variable) and changing its argument count,
Let's see if I understand. I have attaached a new version that supposes to do this. I tested and seems to work. Is it better? If you agree, I can put a nice comment and commit.
or creating a method directly (look at the generate: method) would be much faster. Speed can be important in the debugger.
I am not sure if I understood. Maybe you mean something along the lines of:
MethodNode new selector: #tryNamedPrimitive:arg2: arguments: (OrderedCollection with: (TempVariableNode new name: 'arg1' index: 0 type: 2 scope: 0; nowHasDef; nowHasRef; beMethodArg; yourself) with: (TempVariableNode new name: 'arg1' index: 0 type: 2 scope: 0; nowHasDef; nowHasRef; beMethodArg; yourself)) precedence: 3 temporaries: #() block: BlockNode new encoder: (EncoderForV3PlusClosures new instVarNamed: 'class' put: UndefinedObject; yourself) primitive: 117
and then send #generate:
but I get lost to get this working...the previous solution seems easier.
Thanks!
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
What what about temporally (to be removed in the future) just when we are using older VMs?
If temporary, then fine. But its all work :)
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
Why do we need them in ProtoObject? >
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
> Because I'm not sure that adding primitive to VM is always a good > solution. >
Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:.
HTH Eliot
> Stef > > > On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck < > marianopeck@gmail.com> wrote: > > Hi guys. I usually like to take a look to ProtoObject and see what > is really needed for the minimal object. But having 30% of the methods > being #tryNamedPrimitive:with: * is not fun. > > So...I wonder, do you think there could be another way so that to > avoid having all those methods in ProtoObject ? > > > > Yes there is. I implemented primitive 218 in Cog, > primitiveDoNamedPrimitiveWithArgs, which is accessed via > > > > > > tryNamedPrimitiveIn: aCompiledMethod for: aReceiver > withArgs: arguments > > | selector theMethod spec receiverClass | > > <primitive: 218 error: ec> > > ec ifNotNil: > > ["If ec is an integer other than -1 > there was a problem with primitive 218, > > not with the external primitive > itself. -1 indicates a generic failure (where > > ec should be nil) but ec = nil > means primitive 218 is not implemented. So > > interpret -1 to mean the external > primitive failed with a nil error code." > > ec isInteger ifTrue: > > [ec = -1 > > ifTrue: [ec := nil] > > ifFalse: [self > primitiveFailed]]. > > ^{PrimitiveFailToken. ec}]. > > "Assume a nil error code implies the > primitive is not implemented and fall back on the old code." > > "Hack. Attempt to execute the named > primitive from the given compiled method" > > arguments size > 8 ifTrue: > > [^{PrimitiveFailToken. nil}]. > > selector := #( > > tryNamedPrimitive > > tryNamedPrimitive: > > tryNamedPrimitive:with: > > tryNamedPrimitive:with:with: > > tryNamedPrimitive:with:with:with: > > > tryNamedPrimitive:with:with:with:with: > > > tryNamedPrimitive:with:with:with:with:with: > > > tryNamedPrimitive:with:with:with:with:with:with: > > > tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. > > receiverClass := self objectClass: aReceiver. > > theMethod := receiverClass lookupSelector: > selector. > > theMethod == nil ifTrue: > > [^{PrimitiveFailToken. nil}]. > > spec := theMethod literalAt: 1. > > spec replaceFrom: 1 to: spec size with: > (aCompiledMethod literalAt: 1) startingAt: 1. > > Smalltalk unbindExternalPrimitives. > > ^self object: aReceiver perform: selector > withArguments: arguments inClass: receiverClass > > > > (cf tryPrimitive: withArgs:) and used in > > > > > > doPrimitive: primitiveIndex method: meth receiver: > receiver args: arguments > > "Simulate a primitive method whose index is > primitiveIndex. The simulated receiver > > and arguments are given as arguments to > this message. Any primitive which provokes > > execution needs to be intercepted and > simulated to avoid execution running away." > > > > | value | > > "If successful, push result and return > resuming context, else ^ { PrimitiveFailToken. errorCode }" > > (primitiveIndex = 19) ifTrue: > > [ToolSet > > debugContext: self > > label:'Code simulation error' > > contents: nil]. > > > > "ContextPart>>blockCopy:; simulated to get > startpc right" > > (primitiveIndex = 80 and: [(self > objectClass: receiver) includesBehavior: ContextPart]) > > ifTrue: [^self push: ((BlockContext > newForMethod: receiver method) > > > home: receiver home > > > startpc: pc + 2 > > > nargs: (arguments at: 1))]. > > (primitiveIndex = 81 and: [(self > objectClass: receiver) == BlockContext]) "BlockContext>>value[:value:...]" > > ifTrue: [^receiver pushArgs: > arguments from: self]. > > (primitiveIndex = 82 and: [(self > objectClass: receiver) == BlockContext]) "BlockContext>>valueWithArguments:" > > ifTrue: [^receiver pushArgs: > arguments first from: self]. > > primitiveIndex = 83 "afr 9/11/1998 19:50" > "Object>>perform:[with:...]" > > ifTrue: [^self send: arguments first > > to: receiver > > with: > arguments allButFirst > > super: > false]. > > primitiveIndex = 84 "afr 9/11/1998 19:50 & > eem 8/18/2009 17:04" "Object>>perform:withArguments:" > > ifTrue: [^self send: arguments first > > to: receiver > > with: > (arguments at: 2) > > startClass: > nil]. > > primitiveIndex = 100 "eem 8/18/2009 16:57" > "Object>>perform:withArguments:inSuperclass:" > > ifTrue: [^self send: arguments first > > to: receiver > > with: > (arguments at: 2) > > startClass: > (arguments at: 3)]. > > > > "Mutex>>primitiveEnterCriticalSection > > > Mutex>>primitiveTestAndSetOwnershipOfCriticalSection" > > (primitiveIndex = 186 or: [primitiveIndex = > 187]) ifTrue: > > [| active effective | > > active := Processor activeProcess. > > effective := active > effectiveProcess. > > "active == effective" > > value := primitiveIndex = 186 > > ifTrue: > [receiver primitiveEnterCriticalSectionOnBehalfOf: effective] > > ifFalse: > [receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: > effective]. > > ^(value isArray > > and: [value size = 2 > > and: [value first == > PrimitiveFailToken]]) > > ifTrue: [value] > > ifFalse: [self push: value]]. > > > > primitiveIndex = 188 ifTrue: "eem 5/27/2008 > 11:10 Object>>withArgs:executeMethod:" > > [^MethodContext > > sender: self > > receiver: receiver > > method: (arguments at: 2) > > arguments: (arguments at: > 1)]. > > > > "Closure primitives" > > (primitiveIndex = 200 and: [self == > receiver]) ifTrue: > > > "ContextPart>>closureCopy:copiedValues:; simulated to get startpc right" > > [^self push: (BlockClosure > > > outerContext: receiver > > > startpc: pc + 2 > > > numArgs: arguments first > > > copiedValues: arguments last)]. > > ((primitiveIndex between: 201 and: 205) > "BlockClosure>>value[:value:...]" > > or: [primitiveIndex between: 221 and: 222]) > ifTrue: "BlockClosure>>valueNoContextSwitch[:]" > > [^receiver > simulateValueWithArguments: arguments caller: self]. > > primitiveIndex = 206 ifTrue: > "BlockClosure>>valueWithArguments:" > > [^receiver > simulateValueWithArguments: arguments first caller: self]. > > > > primitiveIndex = 118 ifTrue: > "tryPrimitive:withArgs:; avoid recursing in the VM" > > [(arguments size = 2 > > and: [arguments first isInteger > > and: [arguments last class == > Array]]) ifFalse: > > [^ContextPart > primitiveFailTokenFor: nil]. > > ^self doPrimitive: arguments first > method: meth receiver: receiver args: arguments last]. > > > > value := primitiveIndex = 120 "FFI method" > > ifTrue: [(meth > literalAt: 1) tryInvokeWithArguments: arguments] > > ifFalse: > > > [primitiveIndex = 117 "named primitives" > > > ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] > > > ifFalse: > > > [receiver tryPrimitive: primitiveIndex withArgs: arguments]]. > > ^(value isArray > > and: [value size = 2 > > and: [value first == > PrimitiveFailToken]]) > > ifTrue: [value] > > ifFalse: [self push: value] > > > > (find attached). But these need implementing in the standard VM > before they can be used in Pharo, Squeak, etc. > > > > > > Thanks > > > > -- > > Mariano > > http://marianopeck.wordpress.com > > > > > > > > > > -- > > best, > > Eliot > > > > >
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
Hi Eliot -
On 1/25/2012 2:43, Eliot Miranda wrote:
On Tue, Jan 24, 2012 at 2:02 PM, Mariano Martinez Peck <marianopeck@gmail.com mailto:marianopeck@gmail.com> wrote:
sorry...here is the attached.
OK. What you've done will work.
I actually quite like it. Getting rid of all those methods in class ProtoObject is definitely a good thing. Is there any downside other than some possible performance issues? I.e., the code should work everywhere, right?
Some criticism: Since the method is new the flushCache should be unneeded. I think either copying a template method (e.g. stored in a class variable) and changing its argument count, or creating a method directly (look at the generate: method) would be much faster. Speed can be important in the debugger.
More important in my view is the misuse of the term "temporal" in the method names. I find it quite confusing to read about "temporal" compiled methods :-)
Cheers, - Andreas
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck <marianopeck@gmail.com <mailto:marianopeck@gmail.com>> wrote: Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor. What what about temporally (to be removed in the future) just when we are using older VMs? If temporary, then fine. But its all work :) Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to: StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName. did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive). is this ok? Thanks Eliot! Why do we need them in ProtoObject? Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80. Because I'm not sure that adding primitive to VM is always a good solution. Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:. HTH Eliot Stef > On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck <marianopeck@gmail.com <mailto:marianopeck@gmail.com>> wrote: > Hi guys. I usually like to take a look to ProtoObject and see what is really needed for the minimal object. But having 30% of the methods being #tryNamedPrimitive:with: * is not fun. > So...I wonder, do you think there could be another way so that to avoid having all those methods in ProtoObject ? > > Yes there is. I implemented primitive 218 in Cog, primitiveDoNamedPrimitiveWithArgs, which is accessed via > > > tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments > | selector theMethod spec receiverClass | > <primitive: 218 error: ec> > ec ifNotNil: > ["If ec is an integer other than -1 there was a problem with primitive 218, > not with the external primitive itself. -1 indicates a generic failure (where > ec should be nil) but ec = nil means primitive 218 is not implemented. So > interpret -1 to mean the external primitive failed with a nil error code." > ec isInteger ifTrue: > [ec = -1 > ifTrue: [ec := nil] > ifFalse: [self primitiveFailed]]. > ^{PrimitiveFailToken. ec}]. > "Assume a nil error code implies the primitive is not implemented and fall back on the old code." > "Hack. Attempt to execute the named primitive from the given compiled method" > arguments size > 8 ifTrue: > [^{PrimitiveFailToken. nil}]. > selector := #( > tryNamedPrimitive > tryNamedPrimitive: > tryNamedPrimitive:with: > tryNamedPrimitive:with:with: > tryNamedPrimitive:with:with:with: > tryNamedPrimitive:with:with:with:with: > tryNamedPrimitive:with:with:with:with:with: > tryNamedPrimitive:with:with:with:with:with:with: > tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. > receiverClass := self objectClass: aReceiver. > theMethod := receiverClass lookupSelector: selector. > theMethod == nil ifTrue: > [^{PrimitiveFailToken. nil}]. > spec := theMethod literalAt: 1. > spec replaceFrom: 1 to: spec size with: (aCompiledMethod literalAt: 1) startingAt: 1. > Smalltalk unbindExternalPrimitives. > ^self object: aReceiver perform: selector withArguments: arguments inClass: receiverClass > > (cf tryPrimitive: withArgs:) and used in > > > doPrimitive: primitiveIndex method: meth receiver: receiver args: arguments > "Simulate a primitive method whose index is primitiveIndex. The simulated receiver > and arguments are given as arguments to this message. Any primitive which provokes > execution needs to be intercepted and simulated to avoid execution running away." > > | value | > "If successful, push result and return resuming context, else ^ { PrimitiveFailToken. errorCode }" > (primitiveIndex = 19) ifTrue: > [ToolSet > debugContext: self > label:'Code simulation error' > contents: nil]. > > "ContextPart>>blockCopy:; simulated to get startpc right" > (primitiveIndex = 80 and: [(self objectClass: receiver) includesBehavior: ContextPart]) > ifTrue: [^self push: ((BlockContext newForMethod: receiver method) > home: receiver home > startpc: pc + 2 > nargs: (arguments at: 1))]. > (primitiveIndex = 81 and: [(self objectClass: receiver) == BlockContext]) "BlockContext>>value[:value:...]" > ifTrue: [^receiver pushArgs: arguments from: self]. > (primitiveIndex = 82 and: [(self objectClass: receiver) == BlockContext]) "BlockContext>>valueWithArguments:" > ifTrue: [^receiver pushArgs: arguments first from: self]. > primitiveIndex = 83 "afr 9/11/1998 19:50" "Object>>perform:[with:...]" > ifTrue: [^self send: arguments first > to: receiver > with: arguments allButFirst > super: false]. > primitiveIndex = 84 "afr 9/11/1998 19:50 & eem 8/18/2009 17:04" "Object>>perform:withArguments:" > ifTrue: [^self send: arguments first > to: receiver > with: (arguments at: 2) > startClass: nil]. > primitiveIndex = 100 "eem 8/18/2009 16:57" "Object>>perform:withArguments:inSuperclass:" > ifTrue: [^self send: arguments first > to: receiver > with: (arguments at: 2) > startClass: (arguments at: 3)]. > > "Mutex>>primitiveEnterCriticalSection > Mutex>>primitiveTestAndSetOwnershipOfCriticalSection" > (primitiveIndex = 186 or: [primitiveIndex = 187]) ifTrue: > [| active effective | > active := Processor activeProcess. > effective := active effectiveProcess. > "active == effective" > value := primitiveIndex = 186 > ifTrue: [receiver primitiveEnterCriticalSectionOnBehalfOf: effective] > ifFalse: [receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: effective]. > ^(value isArray > and: [value size = 2 > and: [value first == PrimitiveFailToken]]) > ifTrue: [value] > ifFalse: [self push: value]]. > > primitiveIndex = 188 ifTrue: "eem 5/27/2008 11:10 Object>>withArgs:executeMethod:" > [^MethodContext > sender: self > receiver: receiver > method: (arguments at: 2) > arguments: (arguments at: 1)]. > > "Closure primitives" > (primitiveIndex = 200 and: [self == receiver]) ifTrue: > "ContextPart>>closureCopy:copiedValues:; simulated to get startpc right" > [^self push: (BlockClosure > outerContext: receiver > startpc: pc + 2 > numArgs: arguments first > copiedValues: arguments last)]. > ((primitiveIndex between: 201 and: 205) "BlockClosure>>value[:value:...]" > or: [primitiveIndex between: 221 and: 222]) ifTrue: "BlockClosure>>valueNoContextSwitch[:]" > [^receiver simulateValueWithArguments: arguments caller: self]. > primitiveIndex = 206 ifTrue: "BlockClosure>>valueWithArguments:" > [^receiver simulateValueWithArguments: arguments first caller: self]. > > primitiveIndex = 118 ifTrue: "tryPrimitive:withArgs:; avoid recursing in the VM" > [(arguments size = 2 > and: [arguments first isInteger > and: [arguments last class == Array]]) ifFalse: > [^ContextPart primitiveFailTokenFor: nil]. > ^self doPrimitive: arguments first method: meth receiver: receiver args: arguments last]. > > value := primitiveIndex = 120 "FFI method" > ifTrue: [(meth literalAt: 1) tryInvokeWithArguments: arguments] > ifFalse: > [primitiveIndex = 117 "named primitives" > ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] > ifFalse: > [receiver tryPrimitive: primitiveIndex withArgs: arguments]]. > ^(value isArray > and: [value size = 2 > and: [value first == PrimitiveFailToken]]) > ifTrue: [value] > ifFalse: [self push: value] > > (find attached). But these need implementing in the standard VM before they can be used in Pharo, Squeak, etc. > > > Thanks > > -- > Mariano > http://marianopeck.wordpress.com > > > > > -- > best, > Eliot > -- best, Eliot -- Mariano http://marianopeck.wordpress.com -- best, Eliot -- Mariano http://marianopeck.wordpress.com -- Mariano http://marianopeck.wordpress.com
-- best, Eliot
On Wed, Jan 25, 2012 at 2:21 PM, Andreas Raab andreas.raab@gmx.de wrote:
Hi Eliot -
On 1/25/2012 2:43, Eliot Miranda wrote:
On Tue, Jan 24, 2012 at 2:02 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
sorry...here is the attached.
OK. What you've done will work.
I actually quite like it. Getting rid of all those methods in class ProtoObject is definitely a good thing. Is there any downside other than some possible performance issues?
With my second attempt (the one of reusing the same compiled method and changing the number of arguments) I guess it is quite fast. I have to do that, but for example, I avoid the lookup. So if I am not missing something I should be more or less as fast as the original version. Am I missing something?
I.e., the code should work everywhere, right?
Yes, because in Cog it will use the primitive and in previous VM the primitive will fail and "rc" will be in nil, and hence the smalltalk code will be executed.
Some criticism: Since the method is new the flushCache should be unneeded. I think either copying a template method (e.g. stored in a class variable) and changing its argument count, or creating a method directly (look at the generate: method) would be much faster. Speed can be important in the debugger.
More important in my view is the misuse of the term "temporal" in the method names. I find it quite confusing to read about "temporal" compiled methods :-)
In my second attempt they are not called temporal. There is #tryNamedPrimitiveTemplateMethod Is it beter?
Thanks.
Cheers,
- Andreas
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
What what about temporally (to be removed in the future) just when we are using older VMs?
If temporary, then fine. But its all work :)
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
Why do we need them in ProtoObject? >
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
> Because I'm not sure that adding primitive to VM is always a good > solution. >
Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:.
HTH Eliot
> Stef > > > On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck < > marianopeck@gmail.com> wrote: > > Hi guys. I usually like to take a look to ProtoObject and see what > is really needed for the minimal object. But having 30% of the methods > being #tryNamedPrimitive:with: * is not fun. > > So...I wonder, do you think there could be another way so that to > avoid having all those methods in ProtoObject ? > > > > Yes there is. I implemented primitive 218 in Cog, > primitiveDoNamedPrimitiveWithArgs, which is accessed via > > > > > > tryNamedPrimitiveIn: aCompiledMethod for: aReceiver > withArgs: arguments > > | selector theMethod spec receiverClass | > > <primitive: 218 error: ec> > > ec ifNotNil: > > ["If ec is an integer other than -1 > there was a problem with primitive 218, > > not with the external primitive > itself. -1 indicates a generic failure (where > > ec should be nil) but ec = nil > means primitive 218 is not implemented. So > > interpret -1 to mean the external > primitive failed with a nil error code." > > ec isInteger ifTrue: > > [ec = -1 > > ifTrue: [ec := nil] > > ifFalse: [self > primitiveFailed]]. > > ^{PrimitiveFailToken. ec}]. > > "Assume a nil error code implies the > primitive is not implemented and fall back on the old code." > > "Hack. Attempt to execute the named > primitive from the given compiled method" > > arguments size > 8 ifTrue: > > [^{PrimitiveFailToken. nil}]. > > selector := #( > > tryNamedPrimitive > > tryNamedPrimitive: > > tryNamedPrimitive:with: > > tryNamedPrimitive:with:with: > > tryNamedPrimitive:with:with:with: > > > tryNamedPrimitive:with:with:with:with: > > > tryNamedPrimitive:with:with:with:with:with: > > > tryNamedPrimitive:with:with:with:with:with:with: > > > tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. > > receiverClass := self objectClass: aReceiver. > > theMethod := receiverClass lookupSelector: > selector. > > theMethod == nil ifTrue: > > [^{PrimitiveFailToken. nil}]. > > spec := theMethod literalAt: 1. > > spec replaceFrom: 1 to: spec size with: > (aCompiledMethod literalAt: 1) startingAt: 1. > > Smalltalk unbindExternalPrimitives. > > ^self object: aReceiver perform: selector > withArguments: arguments inClass: receiverClass > > > > (cf tryPrimitive: withArgs:) and used in > > > > > > doPrimitive: primitiveIndex method: meth receiver: > receiver args: arguments > > "Simulate a primitive method whose index is > primitiveIndex. The simulated receiver > > and arguments are given as arguments to > this message. Any primitive which provokes > > execution needs to be intercepted and > simulated to avoid execution running away." > > > > | value | > > "If successful, push result and return > resuming context, else ^ { PrimitiveFailToken. errorCode }" > > (primitiveIndex = 19) ifTrue: > > [ToolSet > > debugContext: self > > label:'Code simulation error' > > contents: nil]. > > > > "ContextPart>>blockCopy:; simulated to get > startpc right" > > (primitiveIndex = 80 and: [(self > objectClass: receiver) includesBehavior: ContextPart]) > > ifTrue: [^self push: ((BlockContext > newForMethod: receiver method) > > > home: receiver home > > > startpc: pc + 2 > > > nargs: (arguments at: 1))]. > > (primitiveIndex = 81 and: [(self > objectClass: receiver) == BlockContext]) "BlockContext>>value[:value:...]" > > ifTrue: [^receiver pushArgs: > arguments from: self]. > > (primitiveIndex = 82 and: [(self > objectClass: receiver) == BlockContext]) "BlockContext>>valueWithArguments:" > > ifTrue: [^receiver pushArgs: > arguments first from: self]. > > primitiveIndex = 83 "afr 9/11/1998 19:50" > "Object>>perform:[with:...]" > > ifTrue: [^self send: arguments first > > to: receiver > > with: > arguments allButFirst > > super: > false]. > > primitiveIndex = 84 "afr 9/11/1998 19:50 & > eem 8/18/2009 17:04" "Object>>perform:withArguments:" > > ifTrue: [^self send: arguments first > > to: receiver > > with: > (arguments at: 2) > > startClass: > nil]. > > primitiveIndex = 100 "eem 8/18/2009 16:57" > "Object>>perform:withArguments:inSuperclass:" > > ifTrue: [^self send: arguments first > > to: receiver > > with: > (arguments at: 2) > > startClass: > (arguments at: 3)]. > > > > "Mutex>>primitiveEnterCriticalSection > > > Mutex>>primitiveTestAndSetOwnershipOfCriticalSection" > > (primitiveIndex = 186 or: [primitiveIndex = > 187]) ifTrue: > > [| active effective | > > active := Processor activeProcess. > > effective := active > effectiveProcess. > > "active == effective" > > value := primitiveIndex = 186 > > ifTrue: > [receiver primitiveEnterCriticalSectionOnBehalfOf: effective] > > ifFalse: > [receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: > effective]. > > ^(value isArray > > and: [value size = 2 > > and: [value first == > PrimitiveFailToken]]) > > ifTrue: [value] > > ifFalse: [self push: value]]. > > > > primitiveIndex = 188 ifTrue: "eem 5/27/2008 > 11:10 Object>>withArgs:executeMethod:" > > [^MethodContext > > sender: self > > receiver: receiver > > method: (arguments at: 2) > > arguments: (arguments at: > 1)]. > > > > "Closure primitives" > > (primitiveIndex = 200 and: [self == > receiver]) ifTrue: > > > "ContextPart>>closureCopy:copiedValues:; simulated to get startpc right" > > [^self push: (BlockClosure > > > outerContext: receiver > > > startpc: pc + 2 > > > numArgs: arguments first > > > copiedValues: arguments last)]. > > ((primitiveIndex between: 201 and: 205) > "BlockClosure>>value[:value:...]" > > or: [primitiveIndex between: 221 and: 222]) > ifTrue: "BlockClosure>>valueNoContextSwitch[:]" > > [^receiver > simulateValueWithArguments: arguments caller: self]. > > primitiveIndex = 206 ifTrue: > "BlockClosure>>valueWithArguments:" > > [^receiver > simulateValueWithArguments: arguments first caller: self]. > > > > primitiveIndex = 118 ifTrue: > "tryPrimitive:withArgs:; avoid recursing in the VM" > > [(arguments size = 2 > > and: [arguments first isInteger > > and: [arguments last class == > Array]]) ifFalse: > > [^ContextPart > primitiveFailTokenFor: nil]. > > ^self doPrimitive: arguments first > method: meth receiver: receiver args: arguments last]. > > > > value := primitiveIndex = 120 "FFI method" > > ifTrue: [(meth > literalAt: 1) tryInvokeWithArguments: arguments] > > ifFalse: > > > [primitiveIndex = 117 "named primitives" > > > ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] > > > ifFalse: > > > [receiver tryPrimitive: primitiveIndex withArgs: arguments]]. > > ^(value isArray > > and: [value size = 2 > > and: [value first == > PrimitiveFailToken]]) > > ifTrue: [value] > > ifFalse: [self push: value] > > > > (find attached). But these need implementing in the standard VM > before they can be used in Pharo, Squeak, etc. > > > > > > Thanks > > > > -- > > Mariano > > http://marianopeck.wordpress.com > > > > > > > > > > -- > > best, > > Eliot > > > > >
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
On Wed, Jan 25, 2012 at 3:13 AM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
On Wed, Jan 25, 2012 at 2:43 AM, Eliot Miranda eliot.miranda@gmail.comwrote:
On Tue, Jan 24, 2012 at 2:02 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
sorry...here is the attached.
OK. What you've done will work. Some criticism: Since the method is new the flushCache should be unneeded.
True.
I think either copying a template method (e.g. stored in a class variable) and changing its argument count,
Let's see if I understand. I have attaached a new version that supposes to do this. I tested and seems to work. Is it better? If you agree, I can put a nice comment and commit.
or creating a method directly (look at the generate: method) would be much faster. Speed can be important in the debugger.
I am not sure if I understood. Maybe you mean something along the lines of:
MethodNode new selector: #tryNamedPrimitive:arg2: arguments: (OrderedCollection with: (TempVariableNode new name: 'arg1' index: 0 type: 2 scope: 0; nowHasDef; nowHasRef; beMethodArg; yourself) with: (TempVariableNode new name: 'arg1' index: 0 type: 2 scope: 0; nowHasDef; nowHasRef; beMethodArg; yourself)) precedence: 3 temporaries: #() block: BlockNode new encoder: (EncoderForV3PlusClosures new instVarNamed: 'class' put: UndefinedObject; yourself) primitive: 117
and then send #generate:
No, I meant create the method directly as does the body of generate:using:. e.g. if the method looks like
tryNamedPrimitiveWith: a with: b ... with: n <primitive: 'function to be filled in later' module: 'module to be filled in later'> ^ContextPart primitiveFailToken
then it looks something like
tempMethodForNamedPrimitive: functionName inModule: moduleName numArgs: nArgs
literals := { { moduleName. functionName. 0. 0. }. Smalltalk bindingOf: #ContextPart. #primitiveFailToken. self additionalMethodStateForNamedPrimitiveWithNArgs: nArgs. Smalltalk bindingOf: #Object }. bytes := #( 17 "push lit var 1 (ContextPart)" 210 "send lit 2 with 0 args (primitiveFailToken)" 124 "return top" ). method := aCompiledMethodClass newBytes: bytes size trailerBytes: CompiledMethodTrailer empty nArgs: nargs nTemps: nargs nStack: 0 nLits: literals size primitive: 117. 1 to: literals size do: [:i | method literalAt: i put: (literals at: i)]. 0 to: bytes size - 1 do: [:i| method byteAt: method initialPC + i put: (bytes at: i + 1)]. ^method
But since the bytecodes don't change with the argument count it is even faster to create the above, copy it and smash the selector and argument count into the copy. MethodWrappers4.2 has an example of this kind of thing.
but I get lost to get this working...the previous solution seems easier.
Do you want to build Trabants or BMWs? Documentation helps.
Thanks!
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
> Analogously, one needs a way of invoking named primitives in the > debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the > same weaknesses as tryPrimitiveN above. So introducing a primitive to run > named primitives is in keeping with tryPrimitive:withArgs:. Using the > VisualWorks approach is feasible but violates Occam's razor. >
What what about temporally (to be removed in the future) just when we are using older VMs?
If temporary, then fine. But its all work :)
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
> > Why do we need them in ProtoObject? >> > > Once tryNamedPrimitiveIn:for:withArgs: is implemented in all > relevant virtual machines we don't need them. You'll notice that there is > no trace of the tryPrimitiveN methods anymore, even though they're in > Smalltalk-80. > > >> Because I'm not sure that adding primitive to VM is always a good >> solution. >> > > Agreed. But it is in keeping with the primitive for invoking > numbered primitives, tryPrimitive:withArgs:. > > > HTH > Eliot > > >> Stef >> >> > On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck < >> marianopeck@gmail.com> wrote: >> > Hi guys. I usually like to take a look to ProtoObject and see >> what is really needed for the minimal object. But having 30% of the methods >> being #tryNamedPrimitive:with: * is not fun. >> > So...I wonder, do you think there could be another way so that to >> avoid having all those methods in ProtoObject ? >> > >> > Yes there is. I implemented primitive 218 in Cog, >> primitiveDoNamedPrimitiveWithArgs, which is accessed via >> > >> > >> > tryNamedPrimitiveIn: aCompiledMethod for: aReceiver >> withArgs: arguments >> > | selector theMethod spec receiverClass | >> > <primitive: 218 error: ec> >> > ec ifNotNil: >> > ["If ec is an integer other than -1 >> there was a problem with primitive 218, >> > not with the external primitive >> itself. -1 indicates a generic failure (where >> > ec should be nil) but ec = nil >> means primitive 218 is not implemented. So >> > interpret -1 to mean the external >> primitive failed with a nil error code." >> > ec isInteger ifTrue: >> > [ec = -1 >> > ifTrue: [ec := nil] >> > ifFalse: [self >> primitiveFailed]]. >> > ^{PrimitiveFailToken. ec}]. >> > "Assume a nil error code implies the >> primitive is not implemented and fall back on the old code." >> > "Hack. Attempt to execute the named >> primitive from the given compiled method" >> > arguments size > 8 ifTrue: >> > [^{PrimitiveFailToken. nil}]. >> > selector := #( >> > tryNamedPrimitive >> > tryNamedPrimitive: >> > tryNamedPrimitive:with: >> > tryNamedPrimitive:with:with: >> > tryNamedPrimitive:with:with:with: >> > >> tryNamedPrimitive:with:with:with:with: >> > >> tryNamedPrimitive:with:with:with:with:with: >> > >> tryNamedPrimitive:with:with:with:with:with:with: >> > >> tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. >> > receiverClass := self objectClass: >> aReceiver. >> > theMethod := receiverClass lookupSelector: >> selector. >> > theMethod == nil ifTrue: >> > [^{PrimitiveFailToken. nil}]. >> > spec := theMethod literalAt: 1. >> > spec replaceFrom: 1 to: spec size with: >> (aCompiledMethod literalAt: 1) startingAt: 1. >> > Smalltalk unbindExternalPrimitives. >> > ^self object: aReceiver perform: selector >> withArguments: arguments inClass: receiverClass >> > >> > (cf tryPrimitive: withArgs:) and used in >> > >> > >> > doPrimitive: primitiveIndex method: meth receiver: >> receiver args: arguments >> > "Simulate a primitive method whose index is >> primitiveIndex. The simulated receiver >> > and arguments are given as arguments to >> this message. Any primitive which provokes >> > execution needs to be intercepted and >> simulated to avoid execution running away." >> > >> > | value | >> > "If successful, push result and return >> resuming context, else ^ { PrimitiveFailToken. errorCode }" >> > (primitiveIndex = 19) ifTrue: >> > [ToolSet >> > debugContext: self >> > label:'Code simulation >> error' >> > contents: nil]. >> > >> > "ContextPart>>blockCopy:; simulated to get >> startpc right" >> > (primitiveIndex = 80 and: [(self >> objectClass: receiver) includesBehavior: ContextPart]) >> > ifTrue: [^self push: ((BlockContext >> newForMethod: receiver method) >> > >> home: receiver home >> > >> startpc: pc + 2 >> > >> nargs: (arguments at: 1))]. >> > (primitiveIndex = 81 and: [(self >> objectClass: receiver) == BlockContext]) "BlockContext>>value[:value:...]" >> > ifTrue: [^receiver pushArgs: >> arguments from: self]. >> > (primitiveIndex = 82 and: [(self >> objectClass: receiver) == BlockContext]) "BlockContext>>valueWithArguments:" >> > ifTrue: [^receiver pushArgs: >> arguments first from: self]. >> > primitiveIndex = 83 "afr 9/11/1998 19:50" >> "Object>>perform:[with:...]" >> > ifTrue: [^self send: arguments first >> > to: receiver >> > with: >> arguments allButFirst >> > super: >> false]. >> > primitiveIndex = 84 "afr 9/11/1998 19:50 & >> eem 8/18/2009 17:04" "Object>>perform:withArguments:" >> > ifTrue: [^self send: arguments first >> > to: receiver >> > with: >> (arguments at: 2) >> > startClass: >> nil]. >> > primitiveIndex = 100 "eem 8/18/2009 16:57" >> "Object>>perform:withArguments:inSuperclass:" >> > ifTrue: [^self send: arguments first >> > to: receiver >> > with: >> (arguments at: 2) >> > startClass: >> (arguments at: 3)]. >> > >> > "Mutex>>primitiveEnterCriticalSection >> > >> Mutex>>primitiveTestAndSetOwnershipOfCriticalSection" >> > (primitiveIndex = 186 or: [primitiveIndex = >> 187]) ifTrue: >> > [| active effective | >> > active := Processor activeProcess. >> > effective := active >> effectiveProcess. >> > "active == effective" >> > value := primitiveIndex = 186 >> > ifTrue: >> [receiver primitiveEnterCriticalSectionOnBehalfOf: effective] >> > ifFalse: >> [receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: >> effective]. >> > ^(value isArray >> > and: [value size = 2 >> > and: [value first == >> PrimitiveFailToken]]) >> > ifTrue: [value] >> > ifFalse: [self push: >> value]]. >> > >> > primitiveIndex = 188 ifTrue: "eem 5/27/2008 >> 11:10 Object>>withArgs:executeMethod:" >> > [^MethodContext >> > sender: self >> > receiver: receiver >> > method: (arguments at: 2) >> > arguments: (arguments at: >> 1)]. >> > >> > "Closure primitives" >> > (primitiveIndex = 200 and: [self == >> receiver]) ifTrue: >> > >> "ContextPart>>closureCopy:copiedValues:; simulated to get startpc right" >> > [^self push: (BlockClosure >> > >> outerContext: receiver >> > >> startpc: pc + 2 >> > >> numArgs: arguments first >> > >> copiedValues: arguments last)]. >> > ((primitiveIndex between: 201 and: 205) >> "BlockClosure>>value[:value:...]" >> > or: [primitiveIndex between: 221 and: 222]) >> ifTrue: "BlockClosure>>valueNoContextSwitch[:]" >> > [^receiver >> simulateValueWithArguments: arguments caller: self]. >> > primitiveIndex = 206 ifTrue: >> "BlockClosure>>valueWithArguments:" >> > [^receiver >> simulateValueWithArguments: arguments first caller: self]. >> > >> > primitiveIndex = 118 ifTrue: >> "tryPrimitive:withArgs:; avoid recursing in the VM" >> > [(arguments size = 2 >> > and: [arguments first isInteger >> > and: [arguments last class == >> Array]]) ifFalse: >> > [^ContextPart >> primitiveFailTokenFor: nil]. >> > ^self doPrimitive: arguments first >> method: meth receiver: receiver args: arguments last]. >> > >> > value := primitiveIndex = 120 "FFI method" >> > ifTrue: [(meth >> literalAt: 1) tryInvokeWithArguments: arguments] >> > ifFalse: >> > >> [primitiveIndex = 117 "named primitives" >> > >> ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] >> > >> ifFalse: >> > >> [receiver tryPrimitive: primitiveIndex withArgs: arguments]]. >> > ^(value isArray >> > and: [value size = 2 >> > and: [value first == >> PrimitiveFailToken]]) >> > ifTrue: [value] >> > ifFalse: [self push: value] >> > >> > (find attached). But these need implementing in the standard VM >> before they can be used in Pharo, Squeak, etc. >> > >> > >> > Thanks >> > >> > -- >> > Mariano >> > http://marianopeck.wordpress.com >> > >> > >> > >> > >> > -- >> > best, >> > Eliot >> > >> >> >> > > > -- > best, > Eliot > > >
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
On Wed, Jan 25, 2012 at 5:21 AM, Andreas Raab andreas.raab@gmx.de wrote:
Hi Eliot -
On 1/25/2012 2:43, Eliot Miranda wrote:
On Tue, Jan 24, 2012 at 2:02 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
sorry...here is the attached.
OK. What you've done will work.
I actually quite like it. Getting rid of all those methods in class ProtoObject is definitely a good thing. Is there any downside other than some possible performance issues? I.e., the code should work everywhere, right?
Right. The primitive I implemented at Qwaq/Teleplace (ContextPart>>#tryNamedPrimitiveIn:for:withArgs:) makes these methods obsolete but since this primitive isn't yet in the standard VM the body uses those old methods in case the primitive doesn't exist. As soon as the primitive is in the standard VM as well there's neither no need for those methods in ProtoObject nor the need to synthesize a method on each use. The synthesizing a method approach has a nice conceptual simplicity. It doesn't need a special primitive, and uses machinery that already exists. But IIRC we decided to go the primitive route because ContextPart>>#tryPrimitiveFor:receiver:args: already existed and provided a model.
Some criticism: Since the method is new the flushCache should be unneeded. I think either copying a template method (e.g. stored in a class variable) and changing its argument count, or creating a method directly (look at the generate: method) would be much faster. Speed can be important in the debugger.
More important in my view is the misuse of the term "temporal" in the method names. I find it quite confusing to read about "temporal" compiled methods :-)
Cheers,
- Andreas
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
What what about temporally (to be removed in the future) just when we are using older VMs?
If temporary, then fine. But its all work :)
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
Why do we need them in ProtoObject? >
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
> Because I'm not sure that adding primitive to VM is always a good > solution. >
Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:.
HTH Eliot
> Stef > > > On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck < > marianopeck@gmail.com> wrote: > > Hi guys. I usually like to take a look to ProtoObject and see what > is really needed for the minimal object. But having 30% of the methods > being #tryNamedPrimitive:with: * is not fun. > > So...I wonder, do you think there could be another way so that to > avoid having all those methods in ProtoObject ? > > > > Yes there is. I implemented primitive 218 in Cog, > primitiveDoNamedPrimitiveWithArgs, which is accessed via > > > > > > tryNamedPrimitiveIn: aCompiledMethod for: aReceiver > withArgs: arguments > > | selector theMethod spec receiverClass | > > <primitive: 218 error: ec> > > ec ifNotNil: > > ["If ec is an integer other than -1 > there was a problem with primitive 218, > > not with the external primitive > itself. -1 indicates a generic failure (where > > ec should be nil) but ec = nil > means primitive 218 is not implemented. So > > interpret -1 to mean the external > primitive failed with a nil error code." > > ec isInteger ifTrue: > > [ec = -1 > > ifTrue: [ec := nil] > > ifFalse: [self > primitiveFailed]]. > > ^{PrimitiveFailToken. ec}]. > > "Assume a nil error code implies the > primitive is not implemented and fall back on the old code." > > "Hack. Attempt to execute the named > primitive from the given compiled method" > > arguments size > 8 ifTrue: > > [^{PrimitiveFailToken. nil}]. > > selector := #( > > tryNamedPrimitive > > tryNamedPrimitive: > > tryNamedPrimitive:with: > > tryNamedPrimitive:with:with: > > tryNamedPrimitive:with:with:with: > > > tryNamedPrimitive:with:with:with:with: > > > tryNamedPrimitive:with:with:with:with:with: > > > tryNamedPrimitive:with:with:with:with:with:with: > > > tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. > > receiverClass := self objectClass: aReceiver. > > theMethod := receiverClass lookupSelector: > selector. > > theMethod == nil ifTrue: > > [^{PrimitiveFailToken. nil}]. > > spec := theMethod literalAt: 1. > > spec replaceFrom: 1 to: spec size with: > (aCompiledMethod literalAt: 1) startingAt: 1. > > Smalltalk unbindExternalPrimitives. > > ^self object: aReceiver perform: selector > withArguments: arguments inClass: receiverClass > > > > (cf tryPrimitive: withArgs:) and used in > > > > > > doPrimitive: primitiveIndex method: meth receiver: > receiver args: arguments > > "Simulate a primitive method whose index is > primitiveIndex. The simulated receiver > > and arguments are given as arguments to > this message. Any primitive which provokes > > execution needs to be intercepted and > simulated to avoid execution running away." > > > > | value | > > "If successful, push result and return > resuming context, else ^ { PrimitiveFailToken. errorCode }" > > (primitiveIndex = 19) ifTrue: > > [ToolSet > > debugContext: self > > label:'Code simulation error' > > contents: nil]. > > > > "ContextPart>>blockCopy:; simulated to get > startpc right" > > (primitiveIndex = 80 and: [(self > objectClass: receiver) includesBehavior: ContextPart]) > > ifTrue: [^self push: ((BlockContext > newForMethod: receiver method) > > > home: receiver home > > > startpc: pc + 2 > > > nargs: (arguments at: 1))]. > > (primitiveIndex = 81 and: [(self > objectClass: receiver) == BlockContext]) "BlockContext>>value[:value:...]" > > ifTrue: [^receiver pushArgs: > arguments from: self]. > > (primitiveIndex = 82 and: [(self > objectClass: receiver) == BlockContext]) "BlockContext>>valueWithArguments:" > > ifTrue: [^receiver pushArgs: > arguments first from: self]. > > primitiveIndex = 83 "afr 9/11/1998 19:50" > "Object>>perform:[with:...]" > > ifTrue: [^self send: arguments first > > to: receiver > > with: > arguments allButFirst > > super: > false]. > > primitiveIndex = 84 "afr 9/11/1998 19:50 & > eem 8/18/2009 17:04" "Object>>perform:withArguments:" > > ifTrue: [^self send: arguments first > > to: receiver > > with: > (arguments at: 2) > > startClass: > nil]. > > primitiveIndex = 100 "eem 8/18/2009 16:57" > "Object>>perform:withArguments:inSuperclass:" > > ifTrue: [^self send: arguments first > > to: receiver > > with: > (arguments at: 2) > > startClass: > (arguments at: 3)]. > > > > "Mutex>>primitiveEnterCriticalSection > > > Mutex>>primitiveTestAndSetOwnershipOfCriticalSection" > > (primitiveIndex = 186 or: [primitiveIndex = > 187]) ifTrue: > > [| active effective | > > active := Processor activeProcess. > > effective := active > effectiveProcess. > > "active == effective" > > value := primitiveIndex = 186 > > ifTrue: > [receiver primitiveEnterCriticalSectionOnBehalfOf: effective] > > ifFalse: > [receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: > effective]. > > ^(value isArray > > and: [value size = 2 > > and: [value first == > PrimitiveFailToken]]) > > ifTrue: [value] > > ifFalse: [self push: value]]. > > > > primitiveIndex = 188 ifTrue: "eem 5/27/2008 > 11:10 Object>>withArgs:executeMethod:" > > [^MethodContext > > sender: self > > receiver: receiver > > method: (arguments at: 2) > > arguments: (arguments at: > 1)]. > > > > "Closure primitives" > > (primitiveIndex = 200 and: [self == > receiver]) ifTrue: > > > "ContextPart>>closureCopy:copiedValues:; simulated to get startpc right" > > [^self push: (BlockClosure > > > outerContext: receiver > > > startpc: pc + 2 > > > numArgs: arguments first > > > copiedValues: arguments last)]. > > ((primitiveIndex between: 201 and: 205) > "BlockClosure>>value[:value:...]" > > or: [primitiveIndex between: 221 and: 222]) > ifTrue: "BlockClosure>>valueNoContextSwitch[:]" > > [^receiver > simulateValueWithArguments: arguments caller: self]. > > primitiveIndex = 206 ifTrue: > "BlockClosure>>valueWithArguments:" > > [^receiver > simulateValueWithArguments: arguments first caller: self]. > > > > primitiveIndex = 118 ifTrue: > "tryPrimitive:withArgs:; avoid recursing in the VM" > > [(arguments size = 2 > > and: [arguments first isInteger > > and: [arguments last class == > Array]]) ifFalse: > > [^ContextPart > primitiveFailTokenFor: nil]. > > ^self doPrimitive: arguments first > method: meth receiver: receiver args: arguments last]. > > > > value := primitiveIndex = 120 "FFI method" > > ifTrue: [(meth > literalAt: 1) tryInvokeWithArguments: arguments] > > ifFalse: > > > [primitiveIndex = 117 "named primitives" > > > ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] > > > ifFalse: > > > [receiver tryPrimitive: primitiveIndex withArgs: arguments]]. > > ^(value isArray > > and: [value size = 2 > > and: [value first == > PrimitiveFailToken]]) > > ifTrue: [value] > > ifFalse: [self push: value] > > > > (find attached). But these need implementing in the standard VM > before they can be used in Pharo, Squeak, etc. > > > > > > Thanks > > > > -- > > Mariano > > http://marianopeck.wordpress.com > > > > > > > > > > -- > > best, > > Eliot > > > > >
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
On Wed, Jan 25, 2012 at 6:40 PM, Eliot Miranda eliot.miranda@gmail.comwrote:
On Wed, Jan 25, 2012 at 3:13 AM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
On Wed, Jan 25, 2012 at 2:43 AM, Eliot Miranda eliot.miranda@gmail.comwrote:
On Tue, Jan 24, 2012 at 2:02 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
sorry...here is the attached.
OK. What you've done will work. Some criticism: Since the method is new the flushCache should be unneeded.
True.
I think either copying a template method (e.g. stored in a class variable) and changing its argument count,
Let's see if I understand. I have attaached a new version that supposes to do this. I tested and seems to work. Is it better? If you agree, I can put a nice comment and commit.
or creating a method directly (look at the generate: method) would be much faster. Speed can be important in the debugger.
I am not sure if I understood. Maybe you mean something along the lines of:
MethodNode new selector: #tryNamedPrimitive:arg2: arguments: (OrderedCollection with: (TempVariableNode new name: 'arg1' index: 0 type: 2 scope: 0; nowHasDef; nowHasRef; beMethodArg; yourself) with: (TempVariableNode new name: 'arg1' index: 0 type: 2 scope: 0; nowHasDef; nowHasRef; beMethodArg; yourself)) precedence: 3 temporaries: #() block: BlockNode new encoder: (EncoderForV3PlusClosures new instVarNamed: 'class' put: UndefinedObject; yourself) primitive: 117
and then send #generate:
No, I meant create the method directly as does the body of generate:using:. e.g. if the method looks like
tryNamedPrimitiveWith: a with: b ... with: n <primitive: 'function to be filled in later' module: 'module to be filled in later'> ^ContextPart primitiveFailToken
then it looks something like
tempMethodForNamedPrimitive: functionName inModule: moduleName numArgs: nArgs
literals := { { moduleName. functionName. 0. 0. }. Smalltalk bindingOf: #ContextPart. #primitiveFailToken. self additionalMethodStateForNamedPrimitiveWithNArgs: nArgs. Smalltalk bindingOf: #Object }. bytes := #( 17 "push lit var 1 (ContextPart)" 210 "send lit 2 with 0 args (primitiveFailToken)" 124 "return top" ). method := aCompiledMethodClass newBytes: bytes size trailerBytes: CompiledMethodTrailer empty nArgs: nargs nTemps: nargs nStack: 0 nLits: literals size primitive: 117. 1 to: literals size do: [:i | method literalAt: i put: (literals at: i)]. 0 to: bytes size - 1 do: [:i| method byteAt: method initialPC + i put: (bytes at: i + 1)]. ^method
But since the bytecodes don't change with the argument count it is even faster to create the above, copy it and smash the selector and argument count into the copy. MethodWrappers4.2 has an example of this kind of thing.
Ok. I will try to give it a try to this approach as well. But what happened with the version I attached in the previous email that uses a template method and just change the arguments number? It looks quite fast since the only thing I have to do is to set the arguments number (which are bitAnd:, bitShift: and bitOr: only), and quite easy to understand at the same time.
but I get lost to get this working...the previous solution seems easier.
Do you want to build Trabants or BMWs?
I didn't understand. Which solution which be each car?
Documentation helps.
I didn't understand either. If you refer to the fact I didn't put comments in the .cs I attached, I explicitly said in my previous email "If you agree, I can put a nice comment and commit.". It already took me the whole morning (yes, I am completly noob here) to do the version of changing the argument count, so I was waiting the confirmation in the solution before documenting it and make it better.
Thanks!
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
>> Analogously, one needs a way of invoking named primitives in the >> debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the >> same weaknesses as tryPrimitiveN above. So introducing a primitive to run >> named primitives is in keeping with tryPrimitive:withArgs:. Using the >> VisualWorks approach is feasible but violates Occam's razor. >> > > What what about temporally (to be removed in the future) just when > we are using older VMs? >
If temporary, then fine. But its all work :)
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
> > >> >> Why do we need them in ProtoObject? >>> >> >> Once tryNamedPrimitiveIn:for:withArgs: is implemented in all >> relevant virtual machines we don't need them. You'll notice that there is >> no trace of the tryPrimitiveN methods anymore, even though they're in >> Smalltalk-80. >> >> >>> Because I'm not sure that adding primitive to VM is always a good >>> solution. >>> >> >> Agreed. But it is in keeping with the primitive for invoking >> numbered primitives, tryPrimitive:withArgs:. >> >> >> HTH >> Eliot >> >> >>> Stef >>> >>> > On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck < >>> marianopeck@gmail.com> wrote: >>> > Hi guys. I usually like to take a look to ProtoObject and see >>> what is really needed for the minimal object. But having 30% of the methods >>> being #tryNamedPrimitive:with: * is not fun. >>> > So...I wonder, do you think there could be another way so that >>> to avoid having all those methods in ProtoObject ? >>> > >>> > Yes there is. I implemented primitive 218 in Cog, >>> primitiveDoNamedPrimitiveWithArgs, which is accessed via >>> > >>> > >>> > tryNamedPrimitiveIn: aCompiledMethod for: >>> aReceiver withArgs: arguments >>> > | selector theMethod spec receiverClass | >>> > <primitive: 218 error: ec> >>> > ec ifNotNil: >>> > ["If ec is an integer other than >>> -1 there was a problem with primitive 218, >>> > not with the external primitive >>> itself. -1 indicates a generic failure (where >>> > ec should be nil) but ec = nil >>> means primitive 218 is not implemented. So >>> > interpret -1 to mean the >>> external primitive failed with a nil error code." >>> > ec isInteger ifTrue: >>> > [ec = -1 >>> > ifTrue: [ec := nil] >>> > ifFalse: [self >>> primitiveFailed]]. >>> > ^{PrimitiveFailToken. ec}]. >>> > "Assume a nil error code implies the >>> primitive is not implemented and fall back on the old code." >>> > "Hack. Attempt to execute the named >>> primitive from the given compiled method" >>> > arguments size > 8 ifTrue: >>> > [^{PrimitiveFailToken. nil}]. >>> > selector := #( >>> > tryNamedPrimitive >>> > tryNamedPrimitive: >>> > tryNamedPrimitive:with: >>> > tryNamedPrimitive:with:with: >>> > tryNamedPrimitive:with:with:with: >>> > >>> tryNamedPrimitive:with:with:with:with: >>> > >>> tryNamedPrimitive:with:with:with:with:with: >>> > >>> tryNamedPrimitive:with:with:with:with:with:with: >>> > >>> tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. >>> > receiverClass := self objectClass: >>> aReceiver. >>> > theMethod := receiverClass lookupSelector: >>> selector. >>> > theMethod == nil ifTrue: >>> > [^{PrimitiveFailToken. nil}]. >>> > spec := theMethod literalAt: 1. >>> > spec replaceFrom: 1 to: spec size with: >>> (aCompiledMethod literalAt: 1) startingAt: 1. >>> > Smalltalk unbindExternalPrimitives. >>> > ^self object: aReceiver perform: selector >>> withArguments: arguments inClass: receiverClass >>> > >>> > (cf tryPrimitive: withArgs:) and used in >>> > >>> > >>> > doPrimitive: primitiveIndex method: meth receiver: >>> receiver args: arguments >>> > "Simulate a primitive method whose index >>> is primitiveIndex. The simulated receiver >>> > and arguments are given as arguments to >>> this message. Any primitive which provokes >>> > execution needs to be intercepted and >>> simulated to avoid execution running away." >>> > >>> > | value | >>> > "If successful, push result and return >>> resuming context, else ^ { PrimitiveFailToken. errorCode }" >>> > (primitiveIndex = 19) ifTrue: >>> > [ToolSet >>> > debugContext: self >>> > label:'Code simulation >>> error' >>> > contents: nil]. >>> > >>> > "ContextPart>>blockCopy:; simulated to get >>> startpc right" >>> > (primitiveIndex = 80 and: [(self >>> objectClass: receiver) includesBehavior: ContextPart]) >>> > ifTrue: [^self push: >>> ((BlockContext newForMethod: receiver method) >>> > >>> home: receiver home >>> > >>> startpc: pc + 2 >>> > >>> nargs: (arguments at: 1))]. >>> > (primitiveIndex = 81 and: [(self >>> objectClass: receiver) == BlockContext]) "BlockContext>>value[:value:...]" >>> > ifTrue: [^receiver pushArgs: >>> arguments from: self]. >>> > (primitiveIndex = 82 and: [(self >>> objectClass: receiver) == BlockContext]) "BlockContext>>valueWithArguments:" >>> > ifTrue: [^receiver pushArgs: >>> arguments first from: self]. >>> > primitiveIndex = 83 "afr 9/11/1998 19:50" >>> "Object>>perform:[with:...]" >>> > ifTrue: [^self send: arguments >>> first >>> > to: >>> receiver >>> > with: >>> arguments allButFirst >>> > super: >>> false]. >>> > primitiveIndex = 84 "afr 9/11/1998 19:50 & >>> eem 8/18/2009 17:04" "Object>>perform:withArguments:" >>> > ifTrue: [^self send: arguments >>> first >>> > to: >>> receiver >>> > with: >>> (arguments at: 2) >>> > >>> startClass: nil]. >>> > primitiveIndex = 100 "eem 8/18/2009 16:57" >>> "Object>>perform:withArguments:inSuperclass:" >>> > ifTrue: [^self send: arguments >>> first >>> > to: >>> receiver >>> > with: >>> (arguments at: 2) >>> > >>> startClass: (arguments at: 3)]. >>> > >>> > "Mutex>>primitiveEnterCriticalSection >>> > >>> Mutex>>primitiveTestAndSetOwnershipOfCriticalSection" >>> > (primitiveIndex = 186 or: [primitiveIndex >>> = 187]) ifTrue: >>> > [| active effective | >>> > active := Processor activeProcess. >>> > effective := active >>> effectiveProcess. >>> > "active == effective" >>> > value := primitiveIndex = 186 >>> > ifTrue: >>> [receiver primitiveEnterCriticalSectionOnBehalfOf: effective] >>> > ifFalse: >>> [receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: >>> effective]. >>> > ^(value isArray >>> > and: [value size = 2 >>> > and: [value first == >>> PrimitiveFailToken]]) >>> > ifTrue: [value] >>> > ifFalse: [self push: >>> value]]. >>> > >>> > primitiveIndex = 188 ifTrue: "eem >>> 5/27/2008 11:10 Object>>withArgs:executeMethod:" >>> > [^MethodContext >>> > sender: self >>> > receiver: receiver >>> > method: (arguments at: 2) >>> > arguments: (arguments at: >>> 1)]. >>> > >>> > "Closure primitives" >>> > (primitiveIndex = 200 and: [self == >>> receiver]) ifTrue: >>> > >>> "ContextPart>>closureCopy:copiedValues:; simulated to get startpc right" >>> > [^self push: (BlockClosure >>> > >>> outerContext: receiver >>> > >>> startpc: pc + 2 >>> > >>> numArgs: arguments first >>> > >>> copiedValues: arguments last)]. >>> > ((primitiveIndex between: 201 and: 205) >>> "BlockClosure>>value[:value:...]" >>> > or: [primitiveIndex between: 221 and: >>> 222]) ifTrue: "BlockClosure>>valueNoContextSwitch[:]" >>> > [^receiver >>> simulateValueWithArguments: arguments caller: self]. >>> > primitiveIndex = 206 ifTrue: >>> "BlockClosure>>valueWithArguments:" >>> > [^receiver >>> simulateValueWithArguments: arguments first caller: self]. >>> > >>> > primitiveIndex = 118 ifTrue: >>> "tryPrimitive:withArgs:; avoid recursing in the VM" >>> > [(arguments size = 2 >>> > and: [arguments first isInteger >>> > and: [arguments last class == >>> Array]]) ifFalse: >>> > [^ContextPart >>> primitiveFailTokenFor: nil]. >>> > ^self doPrimitive: arguments >>> first method: meth receiver: receiver args: arguments last]. >>> > >>> > value := primitiveIndex = 120 "FFI method" >>> > ifTrue: [(meth >>> literalAt: 1) tryInvokeWithArguments: arguments] >>> > ifFalse: >>> > >>> [primitiveIndex = 117 "named primitives" >>> > >>> ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] >>> > >>> ifFalse: >>> > >>> [receiver tryPrimitive: primitiveIndex withArgs: arguments]]. >>> > ^(value isArray >>> > and: [value size = 2 >>> > and: [value first == >>> PrimitiveFailToken]]) >>> > ifTrue: [value] >>> > ifFalse: [self push: value] >>> > >>> > (find attached). But these need implementing in the standard VM >>> before they can be used in Pharo, Squeak, etc. >>> > >>> > >>> > Thanks >>> > >>> > -- >>> > Mariano >>> > http://marianopeck.wordpress.com >>> > >>> > >>> > >>> > >>> > -- >>> > best, >>> > Eliot >>> > >>> >>> >>> >> >> >> -- >> best, >> Eliot >> >> >> > > > -- > Mariano > http://marianopeck.wordpress.com > > >
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
On Wed, Jan 25, 2012 at 9:57 AM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
On Wed, Jan 25, 2012 at 6:40 PM, Eliot Miranda eliot.miranda@gmail.comwrote:
On Wed, Jan 25, 2012 at 3:13 AM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
On Wed, Jan 25, 2012 at 2:43 AM, Eliot Miranda eliot.miranda@gmail.comwrote:
On Tue, Jan 24, 2012 at 2:02 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
sorry...here is the attached.
OK. What you've done will work. Some criticism: Since the method is new the flushCache should be unneeded.
True.
I think either copying a template method (e.g. stored in a class variable) and changing its argument count,
Let's see if I understand. I have attaached a new version that supposes to do this. I tested and seems to work. Is it better? If you agree, I can put a nice comment and commit.
or creating a method directly (look at the generate: method) would be much faster. Speed can be important in the debugger.
I am not sure if I understood. Maybe you mean something along the lines of:
MethodNode new selector: #tryNamedPrimitive:arg2: arguments: (OrderedCollection with: (TempVariableNode new name: 'arg1' index: 0 type: 2 scope: 0; nowHasDef; nowHasRef; beMethodArg; yourself) with: (TempVariableNode new name: 'arg1' index: 0 type: 2 scope: 0; nowHasDef; nowHasRef; beMethodArg; yourself)) precedence: 3 temporaries: #() block: BlockNode new encoder: (EncoderForV3PlusClosures new instVarNamed: 'class' put: UndefinedObject; yourself) primitive: 117
and then send #generate:
No, I meant create the method directly as does the body of generate:using:. e.g. if the method looks like
tryNamedPrimitiveWith: a with: b ... with: n <primitive: 'function to be filled in later' module: 'module to be filled in later'> ^ContextPart primitiveFailToken
then it looks something like
tempMethodForNamedPrimitive: functionName inModule: moduleName numArgs: nArgs
literals := { { moduleName. functionName. 0. 0. }. Smalltalk bindingOf: #ContextPart. #primitiveFailToken. self additionalMethodStateForNamedPrimitiveWithNArgs: nArgs. Smalltalk bindingOf: #Object }. bytes := #( 17 "push lit var 1 (ContextPart)" 210 "send lit 2 with 0 args (primitiveFailToken)" 124 "return top" ). method := aCompiledMethodClass newBytes: bytes size trailerBytes: CompiledMethodTrailer empty nArgs: nargs nTemps: nargs nStack: 0 nLits: literals size primitive: 117. 1 to: literals size do: [:i | method literalAt: i put: (literals at: i)]. 0 to: bytes size - 1 do: [:i| method byteAt: method initialPC + i put: (bytes at: i + 1)]. ^method
But since the bytecodes don't change with the argument count it is even faster to create the above, copy it and smash the selector and argument count into the copy. MethodWrappers4.2 has an example of this kind of thing.
Ok. I will try to give it a try to this approach as well. But what happened with the version I attached in the previous email that uses a template method and just change the arguments number?
That would be fine. I didn't look.
It looks quite fast since the only thing I have to do is to set the arguments number (which are bitAnd:, bitShift: and bitOr: only), and quite easy to understand at the same time.
Agreed.
but I get lost to get this working...the previous solution seems easier.
Do you want to build Trabants or BMWs?
I didn't understand. Which solution which be each car?
Compiling from source each time = trabant. Synthesizing directly = vw. Using a template method = bmw.
(vw = VisualWorks ;) )
Documentation helps.
I didn't understand either. If you refer to the fact I didn't put comments in the .cs I attached, I explicitly said in my previous email "If you agree, I can put a nice comment and commit.". It already took me the whole morning (yes, I am completly noob here) to do the version of changing the argument count, so I was waiting the confirmation in the solution before documenting it and make it better.
I mean in general if one builds good quality code that is "clever" then that code needs to be documented. Sometimes simple doesn't cut it (look at the complexity involved in making a VM fast). So if simple doesn't cut it one must document.
Thanks!
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
>>> Analogously, one needs a way of invoking named primitives in the >>> debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the >>> same weaknesses as tryPrimitiveN above. So introducing a primitive to run >>> named primitives is in keeping with tryPrimitive:withArgs:. Using the >>> VisualWorks approach is feasible but violates Occam's razor. >>> >> >> What what about temporally (to be removed in the future) just when >> we are using older VMs? >> > > If temporary, then fine. But its all work :) >
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
> > >> >> >>> >>> Why do we need them in ProtoObject? >>>> >>> >>> Once tryNamedPrimitiveIn:for:withArgs: is implemented in all >>> relevant virtual machines we don't need them. You'll notice that there is >>> no trace of the tryPrimitiveN methods anymore, even though they're in >>> Smalltalk-80. >>> >>> >>>> Because I'm not sure that adding primitive to VM is always a good >>>> solution. >>>> >>> >>> Agreed. But it is in keeping with the primitive for invoking >>> numbered primitives, tryPrimitive:withArgs:. >>> >>> >>> HTH >>> Eliot >>> >>> >>>> Stef >>>> >>>> > On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck < >>>> marianopeck@gmail.com> wrote: >>>> > Hi guys. I usually like to take a look to ProtoObject and see >>>> what is really needed for the minimal object. But having 30% of the methods >>>> being #tryNamedPrimitive:with: * is not fun. >>>> > So...I wonder, do you think there could be another way so that >>>> to avoid having all those methods in ProtoObject ? >>>> > >>>> > Yes there is. I implemented primitive 218 in Cog, >>>> primitiveDoNamedPrimitiveWithArgs, which is accessed via >>>> > >>>> > >>>> > tryNamedPrimitiveIn: aCompiledMethod for: >>>> aReceiver withArgs: arguments >>>> > | selector theMethod spec receiverClass | >>>> > <primitive: 218 error: ec> >>>> > ec ifNotNil: >>>> > ["If ec is an integer other than >>>> -1 there was a problem with primitive 218, >>>> > not with the external primitive >>>> itself. -1 indicates a generic failure (where >>>> > ec should be nil) but ec = nil >>>> means primitive 218 is not implemented. So >>>> > interpret -1 to mean the >>>> external primitive failed with a nil error code." >>>> > ec isInteger ifTrue: >>>> > [ec = -1 >>>> > ifTrue: [ec := >>>> nil] >>>> > ifFalse: [self >>>> primitiveFailed]]. >>>> > ^{PrimitiveFailToken. ec}]. >>>> > "Assume a nil error code implies the >>>> primitive is not implemented and fall back on the old code." >>>> > "Hack. Attempt to execute the named >>>> primitive from the given compiled method" >>>> > arguments size > 8 ifTrue: >>>> > [^{PrimitiveFailToken. nil}]. >>>> > selector := #( >>>> > tryNamedPrimitive >>>> > tryNamedPrimitive: >>>> > tryNamedPrimitive:with: >>>> > tryNamedPrimitive:with:with: >>>> > tryNamedPrimitive:with:with:with: >>>> > >>>> tryNamedPrimitive:with:with:with:with: >>>> > >>>> tryNamedPrimitive:with:with:with:with:with: >>>> > >>>> tryNamedPrimitive:with:with:with:with:with:with: >>>> > >>>> tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. >>>> > receiverClass := self objectClass: >>>> aReceiver. >>>> > theMethod := receiverClass >>>> lookupSelector: selector. >>>> > theMethod == nil ifTrue: >>>> > [^{PrimitiveFailToken. nil}]. >>>> > spec := theMethod literalAt: 1. >>>> > spec replaceFrom: 1 to: spec size with: >>>> (aCompiledMethod literalAt: 1) startingAt: 1. >>>> > Smalltalk unbindExternalPrimitives. >>>> > ^self object: aReceiver perform: selector >>>> withArguments: arguments inClass: receiverClass >>>> > >>>> > (cf tryPrimitive: withArgs:) and used in >>>> > >>>> > >>>> > doPrimitive: primitiveIndex method: meth >>>> receiver: receiver args: arguments >>>> > "Simulate a primitive method whose index >>>> is primitiveIndex. The simulated receiver >>>> > and arguments are given as arguments to >>>> this message. Any primitive which provokes >>>> > execution needs to be intercepted and >>>> simulated to avoid execution running away." >>>> > >>>> > | value | >>>> > "If successful, push result and return >>>> resuming context, else ^ { PrimitiveFailToken. errorCode }" >>>> > (primitiveIndex = 19) ifTrue: >>>> > [ToolSet >>>> > debugContext: self >>>> > label:'Code simulation >>>> error' >>>> > contents: nil]. >>>> > >>>> > "ContextPart>>blockCopy:; simulated to >>>> get startpc right" >>>> > (primitiveIndex = 80 and: [(self >>>> objectClass: receiver) includesBehavior: ContextPart]) >>>> > ifTrue: [^self push: >>>> ((BlockContext newForMethod: receiver method) >>>> > >>>> home: receiver home >>>> > >>>> startpc: pc + 2 >>>> > >>>> nargs: (arguments at: 1))]. >>>> > (primitiveIndex = 81 and: [(self >>>> objectClass: receiver) == BlockContext]) "BlockContext>>value[:value:...]" >>>> > ifTrue: [^receiver pushArgs: >>>> arguments from: self]. >>>> > (primitiveIndex = 82 and: [(self >>>> objectClass: receiver) == BlockContext]) "BlockContext>>valueWithArguments:" >>>> > ifTrue: [^receiver pushArgs: >>>> arguments first from: self]. >>>> > primitiveIndex = 83 "afr 9/11/1998 19:50" >>>> "Object>>perform:[with:...]" >>>> > ifTrue: [^self send: arguments >>>> first >>>> > to: >>>> receiver >>>> > with: >>>> arguments allButFirst >>>> > super: >>>> false]. >>>> > primitiveIndex = 84 "afr 9/11/1998 19:50 >>>> & eem 8/18/2009 17:04" "Object>>perform:withArguments:" >>>> > ifTrue: [^self send: arguments >>>> first >>>> > to: >>>> receiver >>>> > with: >>>> (arguments at: 2) >>>> > >>>> startClass: nil]. >>>> > primitiveIndex = 100 "eem 8/18/2009 >>>> 16:57" "Object>>perform:withArguments:inSuperclass:" >>>> > ifTrue: [^self send: arguments >>>> first >>>> > to: >>>> receiver >>>> > with: >>>> (arguments at: 2) >>>> > >>>> startClass: (arguments at: 3)]. >>>> > >>>> > "Mutex>>primitiveEnterCriticalSection >>>> > >>>> Mutex>>primitiveTestAndSetOwnershipOfCriticalSection" >>>> > (primitiveIndex = 186 or: [primitiveIndex >>>> = 187]) ifTrue: >>>> > [| active effective | >>>> > active := Processor >>>> activeProcess. >>>> > effective := active >>>> effectiveProcess. >>>> > "active == effective" >>>> > value := primitiveIndex = 186 >>>> > ifTrue: >>>> [receiver primitiveEnterCriticalSectionOnBehalfOf: effective] >>>> > ifFalse: >>>> [receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: >>>> effective]. >>>> > ^(value isArray >>>> > and: [value size = 2 >>>> > and: [value first == >>>> PrimitiveFailToken]]) >>>> > ifTrue: [value] >>>> > ifFalse: [self push: >>>> value]]. >>>> > >>>> > primitiveIndex = 188 ifTrue: "eem >>>> 5/27/2008 11:10 Object>>withArgs:executeMethod:" >>>> > [^MethodContext >>>> > sender: self >>>> > receiver: receiver >>>> > method: (arguments at: 2) >>>> > arguments: (arguments at: >>>> 1)]. >>>> > >>>> > "Closure primitives" >>>> > (primitiveIndex = 200 and: [self == >>>> receiver]) ifTrue: >>>> > >>>> "ContextPart>>closureCopy:copiedValues:; simulated to get startpc right" >>>> > [^self push: (BlockClosure >>>> > >>>> outerContext: receiver >>>> > >>>> startpc: pc + 2 >>>> > >>>> numArgs: arguments first >>>> > >>>> copiedValues: arguments last)]. >>>> > ((primitiveIndex between: 201 and: 205) >>>> "BlockClosure>>value[:value:...]" >>>> > or: [primitiveIndex between: 221 and: >>>> 222]) ifTrue: "BlockClosure>>valueNoContextSwitch[:]" >>>> > [^receiver >>>> simulateValueWithArguments: arguments caller: self]. >>>> > primitiveIndex = 206 ifTrue: >>>> "BlockClosure>>valueWithArguments:" >>>> > [^receiver >>>> simulateValueWithArguments: arguments first caller: self]. >>>> > >>>> > primitiveIndex = 118 ifTrue: >>>> "tryPrimitive:withArgs:; avoid recursing in the VM" >>>> > [(arguments size = 2 >>>> > and: [arguments first isInteger >>>> > and: [arguments last class == >>>> Array]]) ifFalse: >>>> > [^ContextPart >>>> primitiveFailTokenFor: nil]. >>>> > ^self doPrimitive: arguments >>>> first method: meth receiver: receiver args: arguments last]. >>>> > >>>> > value := primitiveIndex = 120 "FFI method" >>>> > ifTrue: [(meth >>>> literalAt: 1) tryInvokeWithArguments: arguments] >>>> > ifFalse: >>>> > >>>> [primitiveIndex = 117 "named primitives" >>>> > >>>> ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] >>>> > >>>> ifFalse: >>>> > >>>> [receiver tryPrimitive: primitiveIndex withArgs: arguments]]. >>>> > ^(value isArray >>>> > and: [value size = 2 >>>> > and: [value first == >>>> PrimitiveFailToken]]) >>>> > ifTrue: [value] >>>> > ifFalse: [self push: value] >>>> > >>>> > (find attached). But these need implementing in the standard >>>> VM before they can be used in Pharo, Squeak, etc. >>>> > >>>> > >>>> > Thanks >>>> > >>>> > -- >>>> > Mariano >>>> > http://marianopeck.wordpress.com >>>> > >>>> > >>>> > >>>> > >>>> > -- >>>> > best, >>>> > Eliot >>>> > >>>> >>>> >>>> >>> >>> >>> -- >>> best, >>> Eliot >>> >>> >>> >> >> >> -- >> Mariano >> http://marianopeck.wordpress.com >> >> >> > > > -- > best, > Eliot > > >
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
Ok. I will try to give it a try to this approach as well.
But what happened with the version I attached in the previous email that uses a template method and just change the arguments number?
That would be fine. I didn't look.
Since I am quite newbie here and the change can be quite important, I would really appreciate if someone can take a look to the .cs so that I feel confident to commit it :)
but I get lost to get this working...the previous solution seems easier.
Do you want to build Trabants or BMWs?
I didn't understand. Which solution which be each car?
Compiling from source each time = trabant. Synthesizing directly = vw. Using a template method = bmw.
(vw = VisualWorks ;) )
:):):):)
Nice because it means that we found a fast solution yet quite understandable :)
Documentation helps.
I didn't understand either. If you refer to the fact I didn't put comments in the .cs I attached, I explicitly said in my previous email "If you agree, I can put a nice comment and commit.". It already took me the whole morning (yes, I am completly noob here) to do the version of changing the argument count, so I was waiting the confirmation in the solution before documenting it and make it better.
I mean in general if one builds good quality code that is "clever" then that code needs to be documented. Sometimes simple doesn't cut it (look at the complexity involved in making a VM fast). So if simple doesn't cut it one must document.
Agree. Once someone gives me the OK for my .cs, I will add comments and commit.
Thanks Eliot.
Thanks!
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck < marianopeck@gmail.com> wrote:
> > >>>> Analogously, one needs a way of invoking named primitives in the >>>> debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the >>>> same weaknesses as tryPrimitiveN above. So introducing a primitive to run >>>> named primitives is in keeping with tryPrimitive:withArgs:. Using the >>>> VisualWorks approach is feasible but violates Occam's razor. >>>> >>> >>> What what about temporally (to be removed in the future) just when >>> we are using older VMs? >>> >> >> If temporary, then fine. But its all work :) >> > > > Well...writing papers can be boring ;) > Please, could you take a look to the attached .cs? I tried to do > what I had in mind. Since this part of the system is new for me, I have not > sure it is correct, nor how to test it. So far what I did for testing it is > to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: > arguments > and (temporally) remove the <primitive: 218 error: ec> and define > 'ec' as a temporal variable. Since it will be nil, the following code will > be executed. > Then I put a halt in #open: fileName forWrite: writeMode from > StandardFileStream and then I do it: > FileDirectory default forceNewFileNamed: 'xxxxx'. > Once in the debugger, I went to: > > StandardFileStream retryWithGC:[self primOpen: f writable: > writeMode] > until:[:id| id notNil] > forFileNamed: fileName. > > did a "through" in the close, and be sure I could do "into" and > "step" for #self primOpen: f writable: writeMode (which is a named > primitive). > > is this ok? > > Thanks Eliot! > > > >> >> >>> >>> >>>> >>>> Why do we need them in ProtoObject? >>>>> >>>> >>>> Once tryNamedPrimitiveIn:for:withArgs: is implemented in all >>>> relevant virtual machines we don't need them. You'll notice that there is >>>> no trace of the tryPrimitiveN methods anymore, even though they're in >>>> Smalltalk-80. >>>> >>>> >>>>> Because I'm not sure that adding primitive to VM is always a >>>>> good solution. >>>>> >>>> >>>> Agreed. But it is in keeping with the primitive for invoking >>>> numbered primitives, tryPrimitive:withArgs:. >>>> >>>> >>>> HTH >>>> Eliot >>>> >>>> >>>>> Stef >>>>> >>>>> > On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck < >>>>> marianopeck@gmail.com> wrote: >>>>> > Hi guys. I usually like to take a look to ProtoObject and see >>>>> what is really needed for the minimal object. But having 30% of the methods >>>>> being #tryNamedPrimitive:with: * is not fun. >>>>> > So...I wonder, do you think there could be another way so that >>>>> to avoid having all those methods in ProtoObject ? >>>>> > >>>>> > Yes there is. I implemented primitive 218 in Cog, >>>>> primitiveDoNamedPrimitiveWithArgs, which is accessed via >>>>> > >>>>> > >>>>> > tryNamedPrimitiveIn: aCompiledMethod for: >>>>> aReceiver withArgs: arguments >>>>> > | selector theMethod spec receiverClass | >>>>> > <primitive: 218 error: ec> >>>>> > ec ifNotNil: >>>>> > ["If ec is an integer other than >>>>> -1 there was a problem with primitive 218, >>>>> > not with the external >>>>> primitive itself. -1 indicates a generic failure (where >>>>> > ec should be nil) but ec = nil >>>>> means primitive 218 is not implemented. So >>>>> > interpret -1 to mean the >>>>> external primitive failed with a nil error code." >>>>> > ec isInteger ifTrue: >>>>> > [ec = -1 >>>>> > ifTrue: [ec := >>>>> nil] >>>>> > ifFalse: [self >>>>> primitiveFailed]]. >>>>> > ^{PrimitiveFailToken. ec}]. >>>>> > "Assume a nil error code implies the >>>>> primitive is not implemented and fall back on the old code." >>>>> > "Hack. Attempt to execute the named >>>>> primitive from the given compiled method" >>>>> > arguments size > 8 ifTrue: >>>>> > [^{PrimitiveFailToken. nil}]. >>>>> > selector := #( >>>>> > tryNamedPrimitive >>>>> > tryNamedPrimitive: >>>>> > tryNamedPrimitive:with: >>>>> > tryNamedPrimitive:with:with: >>>>> > tryNamedPrimitive:with:with:with: >>>>> > >>>>> tryNamedPrimitive:with:with:with:with: >>>>> > >>>>> tryNamedPrimitive:with:with:with:with:with: >>>>> > >>>>> tryNamedPrimitive:with:with:with:with:with:with: >>>>> > >>>>> tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. >>>>> > receiverClass := self objectClass: >>>>> aReceiver. >>>>> > theMethod := receiverClass >>>>> lookupSelector: selector. >>>>> > theMethod == nil ifTrue: >>>>> > [^{PrimitiveFailToken. nil}]. >>>>> > spec := theMethod literalAt: 1. >>>>> > spec replaceFrom: 1 to: spec size with: >>>>> (aCompiledMethod literalAt: 1) startingAt: 1. >>>>> > Smalltalk unbindExternalPrimitives. >>>>> > ^self object: aReceiver perform: >>>>> selector withArguments: arguments inClass: receiverClass >>>>> > >>>>> > (cf tryPrimitive: withArgs:) and used in >>>>> > >>>>> > >>>>> > doPrimitive: primitiveIndex method: meth >>>>> receiver: receiver args: arguments >>>>> > "Simulate a primitive method whose index >>>>> is primitiveIndex. The simulated receiver >>>>> > and arguments are given as arguments to >>>>> this message. Any primitive which provokes >>>>> > execution needs to be intercepted and >>>>> simulated to avoid execution running away." >>>>> > >>>>> > | value | >>>>> > "If successful, push result and return >>>>> resuming context, else ^ { PrimitiveFailToken. errorCode }" >>>>> > (primitiveIndex = 19) ifTrue: >>>>> > [ToolSet >>>>> > debugContext: self >>>>> > label:'Code simulation >>>>> error' >>>>> > contents: nil]. >>>>> > >>>>> > "ContextPart>>blockCopy:; simulated to >>>>> get startpc right" >>>>> > (primitiveIndex = 80 and: [(self >>>>> objectClass: receiver) includesBehavior: ContextPart]) >>>>> > ifTrue: [^self push: >>>>> ((BlockContext newForMethod: receiver method) >>>>> > >>>>> home: receiver home >>>>> > >>>>> startpc: pc + 2 >>>>> > >>>>> nargs: (arguments at: 1))]. >>>>> > (primitiveIndex = 81 and: [(self >>>>> objectClass: receiver) == BlockContext]) "BlockContext>>value[:value:...]" >>>>> > ifTrue: [^receiver pushArgs: >>>>> arguments from: self]. >>>>> > (primitiveIndex = 82 and: [(self >>>>> objectClass: receiver) == BlockContext]) "BlockContext>>valueWithArguments:" >>>>> > ifTrue: [^receiver pushArgs: >>>>> arguments first from: self]. >>>>> > primitiveIndex = 83 "afr 9/11/1998 >>>>> 19:50" "Object>>perform:[with:...]" >>>>> > ifTrue: [^self send: arguments >>>>> first >>>>> > to: >>>>> receiver >>>>> > with: >>>>> arguments allButFirst >>>>> > super: >>>>> false]. >>>>> > primitiveIndex = 84 "afr 9/11/1998 19:50 >>>>> & eem 8/18/2009 17:04" "Object>>perform:withArguments:" >>>>> > ifTrue: [^self send: arguments >>>>> first >>>>> > to: >>>>> receiver >>>>> > with: >>>>> (arguments at: 2) >>>>> > >>>>> startClass: nil]. >>>>> > primitiveIndex = 100 "eem 8/18/2009 >>>>> 16:57" "Object>>perform:withArguments:inSuperclass:" >>>>> > ifTrue: [^self send: arguments >>>>> first >>>>> > to: >>>>> receiver >>>>> > with: >>>>> (arguments at: 2) >>>>> > >>>>> startClass: (arguments at: 3)]. >>>>> > >>>>> > "Mutex>>primitiveEnterCriticalSection >>>>> > >>>>> Mutex>>primitiveTestAndSetOwnershipOfCriticalSection" >>>>> > (primitiveIndex = 186 or: >>>>> [primitiveIndex = 187]) ifTrue: >>>>> > [| active effective | >>>>> > active := Processor >>>>> activeProcess. >>>>> > effective := active >>>>> effectiveProcess. >>>>> > "active == effective" >>>>> > value := primitiveIndex = 186 >>>>> > ifTrue: >>>>> [receiver primitiveEnterCriticalSectionOnBehalfOf: effective] >>>>> > ifFalse: >>>>> [receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: >>>>> effective]. >>>>> > ^(value isArray >>>>> > and: [value size = 2 >>>>> > and: [value first == >>>>> PrimitiveFailToken]]) >>>>> > ifTrue: [value] >>>>> > ifFalse: [self push: >>>>> value]]. >>>>> > >>>>> > primitiveIndex = 188 ifTrue: "eem >>>>> 5/27/2008 11:10 Object>>withArgs:executeMethod:" >>>>> > [^MethodContext >>>>> > sender: self >>>>> > receiver: receiver >>>>> > method: (arguments at: 2) >>>>> > arguments: (arguments >>>>> at: 1)]. >>>>> > >>>>> > "Closure primitives" >>>>> > (primitiveIndex = 200 and: [self == >>>>> receiver]) ifTrue: >>>>> > >>>>> "ContextPart>>closureCopy:copiedValues:; simulated to get startpc right" >>>>> > [^self push: (BlockClosure >>>>> > >>>>> outerContext: receiver >>>>> > >>>>> startpc: pc + 2 >>>>> > >>>>> numArgs: arguments first >>>>> > >>>>> copiedValues: arguments last)]. >>>>> > ((primitiveIndex between: 201 and: 205) >>>>> "BlockClosure>>value[:value:...]" >>>>> > or: [primitiveIndex between: 221 and: >>>>> 222]) ifTrue: "BlockClosure>>valueNoContextSwitch[:]" >>>>> > [^receiver >>>>> simulateValueWithArguments: arguments caller: self]. >>>>> > primitiveIndex = 206 ifTrue: >>>>> "BlockClosure>>valueWithArguments:" >>>>> > [^receiver >>>>> simulateValueWithArguments: arguments first caller: self]. >>>>> > >>>>> > primitiveIndex = 118 ifTrue: >>>>> "tryPrimitive:withArgs:; avoid recursing in the VM" >>>>> > [(arguments size = 2 >>>>> > and: [arguments first isInteger >>>>> > and: [arguments last class == >>>>> Array]]) ifFalse: >>>>> > [^ContextPart >>>>> primitiveFailTokenFor: nil]. >>>>> > ^self doPrimitive: arguments >>>>> first method: meth receiver: receiver args: arguments last]. >>>>> > >>>>> > value := primitiveIndex = 120 "FFI >>>>> method" >>>>> > ifTrue: [(meth >>>>> literalAt: 1) tryInvokeWithArguments: arguments] >>>>> > ifFalse: >>>>> > >>>>> [primitiveIndex = 117 "named primitives" >>>>> > >>>>> ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] >>>>> > >>>>> ifFalse: >>>>> > >>>>> [receiver tryPrimitive: primitiveIndex withArgs: arguments]]. >>>>> > ^(value isArray >>>>> > and: [value size = 2 >>>>> > and: [value first == >>>>> PrimitiveFailToken]]) >>>>> > ifTrue: [value] >>>>> > ifFalse: [self push: value] >>>>> > >>>>> > (find attached). But these need implementing in the standard >>>>> VM before they can be used in Pharo, Squeak, etc. >>>>> > >>>>> > >>>>> > Thanks >>>>> > >>>>> > -- >>>>> > Mariano >>>>> > http://marianopeck.wordpress.com >>>>> > >>>>> > >>>>> > >>>>> > >>>>> > -- >>>>> > best, >>>>> > Eliot >>>>> > >>>>> >>>>> >>>>> >>>> >>>> >>>> -- >>>> best, >>>> Eliot >>>> >>>> >>>> >>> >>> >>> -- >>> Mariano >>> http://marianopeck.wordpress.com >>> >>> >>> >> >> >> -- >> best, >> Eliot >> >> >> > > > -- > Mariano > http://marianopeck.wordpress.com > >
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
for the record mariano, VW implementation of method wrapper had a template of method one for each number of possible args and they reserved space in the compiled method to be able to binding the class name (from memory) so installing one was copy of the right template + homeClass + selector (not sure anymore) so it was quite fast and simple to read.
Stef
On Jan 25, 2012, at 7:36 PM, Mariano Martinez Peck wrote:
Ok. I will try to give it a try to this approach as well. But what happened with the version I attached in the previous email that uses a template method and just change the arguments number?
That would be fine. I didn't look.
Since I am quite newbie here and the change can be quite important, I would really appreciate if someone can take a look to the .cs so that I feel confident to commit it :)
but I get lost to get this working...the previous solution seems easier.
Do you want to build Trabants or BMWs?
I didn't understand. Which solution which be each car?
Compiling from source each time = trabant. Synthesizing directly = vw. Using a template method = bmw.
(vw = VisualWorks ;) )
:):):):)
Nice because it means that we found a fast solution yet quite understandable :)
Documentation helps.
I didn't understand either. If you refer to the fact I didn't put comments in the .cs I attached, I explicitly said in my previous email "If you agree, I can put a nice comment and commit.". It already took me the whole morning (yes, I am completly noob here) to do the version of changing the argument count, so I was waiting the confirmation in the solution before documenting it and make it better.
I mean in general if one builds good quality code that is "clever" then that code needs to be documented. Sometimes simple doesn't cut it (look at the complexity involved in making a VM fast). So if simple doesn't cut it one must document.
Agree. Once someone gives me the OK for my .cs, I will add comments and commit.
Thanks Eliot.
Thanks!
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck marianopeck@gmail.com wrote:
Analogously, one needs a way of invoking named primitives in the debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
What what about temporally (to be removed in the future) just when we are using older VMs?
If temporary, then fine. But its all work :)
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments and (temporally) remove the <primitive: 218 error: ec> and define 'ec' as a temporal variable. Since it will be nil, the following code will be executed. Then I put a halt in #open: fileName forWrite: writeMode from StandardFileStream and then I do it: FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step" for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
Why do we need them in ProtoObject?
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
Because I'm not sure that adding primitive to VM is always a good solution.
Agreed. But it is in keeping with the primitive for invoking numbered primitives, tryPrimitive:withArgs:.
HTH Eliot
Stef
On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck marianopeck@gmail.com wrote: Hi guys. I usually like to take a look to ProtoObject and see what is really needed for the minimal object. But having 30% of the methods being #tryNamedPrimitive:with: * is not fun. So...I wonder, do you think there could be another way so that to avoid having all those methods in ProtoObject ?
Yes there is. I implemented primitive 218 in Cog, primitiveDoNamedPrimitiveWithArgs, which is accessed via
tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments | selector theMethod spec receiverClass | <primitive: 218 error: ec> ec ifNotNil: ["If ec is an integer other than -1 there was a problem with primitive 218, not with the external primitive itself. -1 indicates a generic failure (where ec should be nil) but ec = nil means primitive 218 is not implemented. So interpret -1 to mean the external primitive failed with a nil error code." ec isInteger ifTrue: [ec = -1 ifTrue: [ec := nil] ifFalse: [self primitiveFailed]]. ^{PrimitiveFailToken. ec}]. "Assume a nil error code implies the primitive is not implemented and fall back on the old code." "Hack. Attempt to execute the named primitive from the given compiled method" arguments size > 8 ifTrue: [^{PrimitiveFailToken. nil}]. selector := #( tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with: tryNamedPrimitive:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. receiverClass := self objectClass: aReceiver. theMethod := receiverClass lookupSelector: selector. theMethod == nil ifTrue: [^{PrimitiveFailToken. nil}]. spec := theMethod literalAt: 1. spec replaceFrom: 1 to: spec size with: (aCompiledMethod literalAt: 1) startingAt: 1. Smalltalk unbindExternalPrimitives. ^self object: aReceiver perform: selector withArguments: arguments inClass: receiverClass
(cf tryPrimitive: withArgs:) and used in
doPrimitive: primitiveIndex method: meth receiver: receiver args: arguments "Simulate a primitive method whose index is primitiveIndex. The simulated receiver and arguments are given as arguments to this message. Any primitive which provokes execution needs to be intercepted and simulated to avoid execution running away." | value | "If successful, push result and return resuming context, else ^ { PrimitiveFailToken. errorCode }" (primitiveIndex = 19) ifTrue: [ToolSet debugContext: self label:'Code simulation error' contents: nil]. "ContextPart>>blockCopy:; simulated to get startpc right" (primitiveIndex = 80 and: [(self objectClass: receiver) includesBehavior: ContextPart]) ifTrue: [^self push: ((BlockContext newForMethod: receiver method) home: receiver home startpc: pc + 2 nargs: (arguments at: 1))]. (primitiveIndex = 81 and: [(self objectClass: receiver) == BlockContext]) "BlockContext>>value[:value:...]" ifTrue: [^receiver pushArgs: arguments from: self]. (primitiveIndex = 82 and: [(self objectClass: receiver) == BlockContext]) "BlockContext>>valueWithArguments:" ifTrue: [^receiver pushArgs: arguments first from: self]. primitiveIndex = 83 "afr 9/11/1998 19:50" "Object>>perform:[with:...]" ifTrue: [^self send: arguments first to: receiver with: arguments allButFirst super: false]. primitiveIndex = 84 "afr 9/11/1998 19:50 & eem 8/18/2009 17:04" "Object>>perform:withArguments:" ifTrue: [^self send: arguments first to: receiver with: (arguments at: 2) startClass: nil]. primitiveIndex = 100 "eem 8/18/2009 16:57" "Object>>perform:withArguments:inSuperclass:" ifTrue: [^self send: arguments first to: receiver with: (arguments at: 2) startClass: (arguments at: 3)]. "Mutex>>primitiveEnterCriticalSection Mutex>>primitiveTestAndSetOwnershipOfCriticalSection" (primitiveIndex = 186 or: [primitiveIndex = 187]) ifTrue: [| active effective | active := Processor activeProcess. effective := active effectiveProcess. "active == effective" value := primitiveIndex = 186 ifTrue: [receiver primitiveEnterCriticalSectionOnBehalfOf: effective] ifFalse: [receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: effective]. ^(value isArray and: [value size = 2 and: [value first == PrimitiveFailToken]]) ifTrue: [value] ifFalse: [self push: value]]. primitiveIndex = 188 ifTrue: "eem 5/27/2008 11:10 Object>>withArgs:executeMethod:" [^MethodContext sender: self receiver: receiver method: (arguments at: 2) arguments: (arguments at: 1)]. "Closure primitives" (primitiveIndex = 200 and: [self == receiver]) ifTrue: "ContextPart>>closureCopy:copiedValues:; simulated to get startpc right" [^self push: (BlockClosure outerContext: receiver startpc: pc + 2 numArgs: arguments first copiedValues: arguments last)]. ((primitiveIndex between: 201 and: 205) "BlockClosure>>value[:value:...]" or: [primitiveIndex between: 221 and: 222]) ifTrue: "BlockClosure>>valueNoContextSwitch[:]" [^receiver simulateValueWithArguments: arguments caller: self]. primitiveIndex = 206 ifTrue: "BlockClosure>>valueWithArguments:" [^receiver simulateValueWithArguments: arguments first caller: self]. primitiveIndex = 118 ifTrue: "tryPrimitive:withArgs:; avoid recursing in the VM" [(arguments size = 2 and: [arguments first isInteger and: [arguments last class == Array]]) ifFalse: [^ContextPart primitiveFailTokenFor: nil]. ^self doPrimitive: arguments first method: meth receiver: receiver args: arguments last]. value := primitiveIndex = 120 "FFI method" ifTrue: [(meth literalAt: 1) tryInvokeWithArguments: arguments] ifFalse: [primitiveIndex = 117 "named primitives" ifTrue: [self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments] ifFalse: [receiver tryPrimitive: primitiveIndex withArgs: arguments]]. ^(value isArray and: [value size = 2 and: [value first == PrimitiveFailToken]]) ifTrue: [value] ifFalse: [self push: value]
(find attached). But these need implementing in the standard VM before they can be used in Pharo, Squeak, etc.
Thanks
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
On Wed, Jan 25, 2012 at 1:32 PM, stephane ducasse < stephane.ducasse@gmail.com> wrote:
for the record mariano, VW implementation of method wrapper had a template of method one for each number of possible args and they reserved space in the compiled method to be able to binding the class name (from memory) so installing one was copy of the right template + homeClass + selector (not sure anymore) so it was quite fast and simple to read.
and the same thing is in my port to 4.2, MethodWrappers4.2 on squeaksource. See MwMethodWrapper's class instance variable protoMethods.
Stef
On Jan 25, 2012, at 7:36 PM, Mariano Martinez Peck wrote:
Ok. I will try to give it a try to this approach as well. But what happened with the version I attached in the previous email that
uses a template method and just change the arguments number?
That would be fine. I didn't look.
Since I am quite newbie here and the change can be quite important, I
would really appreciate if someone can take a look to the .cs so that I feel confident to commit it :)
but I get lost to get this working...the previous solution seems easier.
Do you want to build Trabants or BMWs?
I didn't understand. Which solution which be each car?
Compiling from source each time = trabant. Synthesizing directly = vw.
Using a template method = bmw.
(vw = VisualWorks ;) )
:):):):)
Nice because it means that we found a fast solution yet quite
understandable :)
Documentation helps.
I didn't understand either. If you refer to the fact I didn't put
comments in the .cs I attached, I explicitly said in my previous email "If you agree, I can put a nice comment and commit.". It already took me the whole morning (yes, I am completly noob here) to do the version of changing the argument count, so I was waiting the confirmation in the solution before documenting it and make it better.
I mean in general if one builds good quality code that is "clever" then
that code needs to be documented. Sometimes simple doesn't cut it (look at the complexity involved in making a VM fast). So if simple doesn't cut it one must document.
Agree. Once someone gives me the OK for my .cs, I will add comments and
commit.
Thanks Eliot.
Thanks!
On Tue, Jan 24, 2012 at 11:01 PM, Mariano Martinez Peck <
marianopeck@gmail.com> wrote:
Analogously, one needs a way of invoking named primitives in the
debugger, and using tryNamedPrimitive[:with:with:...] et al has exactly the same weaknesses as tryPrimitiveN above. So introducing a primitive to run named primitives is in keeping with tryPrimitive:withArgs:. Using the VisualWorks approach is feasible but violates Occam's razor.
What what about temporally (to be removed in the future) just when we
are using older VMs?
If temporary, then fine. But its all work :)
Well...writing papers can be boring ;) Please, could you take a look to the attached .cs? I tried to do what I
had in mind. Since this part of the system is new for me, I have not sure it is correct, nor how to test it. So far what I did for testing it is to take the #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments
and (temporally) remove the <primitive: 218 error: ec> and define 'ec'
as a temporal variable. Since it will be nil, the following code will be executed.
Then I put a halt in #open: fileName forWrite: writeMode from
StandardFileStream and then I do it:
FileDirectory default forceNewFileNamed: 'xxxxx'. Once in the debugger, I went to:
StandardFileStream retryWithGC:[self primOpen: f writable: writeMode] until:[:id| id notNil] forFileNamed: fileName.
did a "through" in the close, and be sure I could do "into" and "step"
for #self primOpen: f writable: writeMode (which is a named primitive).
is this ok?
Thanks Eliot!
Why do we need them in ProtoObject?
Once tryNamedPrimitiveIn:for:withArgs: is implemented in all relevant
virtual machines we don't need them. You'll notice that there is no trace of the tryPrimitiveN methods anymore, even though they're in Smalltalk-80.
Because I'm not sure that adding primitive to VM is always a good
solution.
Agreed. But it is in keeping with the primitive for invoking numbered
primitives, tryPrimitive:withArgs:.
HTH Eliot
Stef
On Mon, Jan 23, 2012 at 8:52 AM, Mariano Martinez Peck <
marianopeck@gmail.com> wrote:
Hi guys. I usually like to take a look to ProtoObject and see what is
really needed for the minimal object. But having 30% of the methods being #tryNamedPrimitive:with: * is not fun.
So...I wonder, do you think there could be another way so that to
avoid having all those methods in ProtoObject ?
Yes there is. I implemented primitive 218 in Cog,
primitiveDoNamedPrimitiveWithArgs, which is accessed via
tryNamedPrimitiveIn: aCompiledMethod for: aReceiver
withArgs: arguments
| selector theMethod spec receiverClass | <primitive: 218 error: ec> ec ifNotNil: ["If ec is an integer other than -1
there was a problem with primitive 218,
not with the external primitive
itself. -1 indicates a generic failure (where
ec should be nil) but ec = nil means
primitive 218 is not implemented. So
interpret -1 to mean the external
primitive failed with a nil error code."
ec isInteger ifTrue: [ec = -1 ifTrue: [ec := nil] ifFalse: [self
primitiveFailed]].
^{PrimitiveFailToken. ec}]. "Assume a nil error code implies the primitive
is not implemented and fall back on the old code."
"Hack. Attempt to execute the named primitive
from the given compiled method"
arguments size > 8 ifTrue: [^{PrimitiveFailToken. nil}]. selector := #( tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:
tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1.
receiverClass := self objectClass: aReceiver. theMethod := receiverClass lookupSelector:
selector.
theMethod == nil ifTrue: [^{PrimitiveFailToken. nil}]. spec := theMethod literalAt: 1. spec replaceFrom: 1 to: spec size with:
(aCompiledMethod literalAt: 1) startingAt: 1.
Smalltalk unbindExternalPrimitives. ^self object: aReceiver perform: selector
withArguments: arguments inClass: receiverClass
(cf tryPrimitive: withArgs:) and used in
doPrimitive: primitiveIndex method: meth receiver:
receiver args: arguments
"Simulate a primitive method whose index is
primitiveIndex. The simulated receiver
and arguments are given as arguments to this
message. Any primitive which provokes
execution needs to be intercepted and simulated
to avoid execution running away."
| value | "If successful, push result and return resuming
context, else ^ { PrimitiveFailToken. errorCode }"
(primitiveIndex = 19) ifTrue: [ToolSet debugContext: self label:'Code simulation error' contents: nil]. "ContextPart>>blockCopy:; simulated to get
startpc right"
(primitiveIndex = 80 and: [(self objectClass:
receiver) includesBehavior: ContextPart])
ifTrue: [^self push: ((BlockContext
newForMethod: receiver method)
home:
receiver home
startpc:
pc + 2
nargs:
(arguments at: 1))].
(primitiveIndex = 81 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>value[:value:...]"
ifTrue: [^receiver pushArgs: arguments
from: self].
(primitiveIndex = 82 and: [(self objectClass:
receiver) == BlockContext]) "BlockContext>>valueWithArguments:"
ifTrue: [^receiver pushArgs: arguments
first from: self].
primitiveIndex = 83 "afr 9/11/1998 19:50"
"Object>>perform:[with:...]"
ifTrue: [^self send: arguments first to: receiver with: arguments
allButFirst
super: false]. primitiveIndex = 84 "afr 9/11/1998 19:50 & eem
8/18/2009 17:04" "Object>>perform:withArguments:"
ifTrue: [^self send: arguments first to: receiver with: (arguments
at: 2)
startClass: nil]. primitiveIndex = 100 "eem 8/18/2009 16:57"
"Object>>perform:withArguments:inSuperclass:"
ifTrue: [^self send: arguments first to: receiver with: (arguments
at: 2)
startClass:
(arguments at: 3)].
"Mutex>>primitiveEnterCriticalSection
Mutex>>primitiveTestAndSetOwnershipOfCriticalSection"
(primitiveIndex = 186 or: [primitiveIndex =
187]) ifTrue:
[| active effective | active := Processor activeProcess. effective := active effectiveProcess. "active == effective" value := primitiveIndex = 186 ifTrue:
[receiver primitiveEnterCriticalSectionOnBehalfOf: effective]
ifFalse:
[receiver primitiveTestAndSetOwnershipOfCriticalSectionOnBehalfOf: effective].
^(value isArray and: [value size = 2 and: [value first ==
PrimitiveFailToken]])
ifTrue: [value] ifFalse: [self push: value]]. primitiveIndex = 188 ifTrue: "eem 5/27/2008
11:10 Object>>withArgs:executeMethod:"
[^MethodContext sender: self receiver: receiver method: (arguments at: 2) arguments: (arguments at: 1)]. "Closure primitives" (primitiveIndex = 200 and: [self == receiver])
ifTrue:
"ContextPart>>closureCopy:copiedValues:;
simulated to get startpc right"
[^self push: (BlockClosure
outerContext: receiver
startpc:
pc + 2
numArgs:
arguments first
copiedValues: arguments last)].
((primitiveIndex between: 201 and: 205)
"BlockClosure>>value[:value:...]"
or: [primitiveIndex between: 221 and: 222])
ifTrue: "BlockClosure>>valueNoContextSwitch[:]"
[^receiver simulateValueWithArguments:
arguments caller: self].
primitiveIndex = 206 ifTrue:
"BlockClosure>>valueWithArguments:"
[^receiver simulateValueWithArguments:
arguments first caller: self].
primitiveIndex = 118 ifTrue:
"tryPrimitive:withArgs:; avoid recursing in the VM"
[(arguments size = 2 and: [arguments first isInteger and: [arguments last class == Array]])
ifFalse:
[^ContextPart
primitiveFailTokenFor: nil].
^self doPrimitive: arguments first
method: meth receiver: receiver args: arguments last].
value := primitiveIndex = 120 "FFI method" ifTrue: [(meth
literalAt: 1) tryInvokeWithArguments: arguments]
ifFalse: [primitiveIndex
= 117 "named primitives"
ifTrue:
[self tryNamedPrimitiveIn: meth for: receiver withArgs: arguments]
ifFalse:
[receiver tryPrimitive: primitiveIndex withArgs: arguments]].
^(value isArray and: [value size = 2 and: [value first == PrimitiveFailToken]]) ifTrue: [value] ifFalse: [self push: value]
(find attached). But these need implementing in the standard VM
before they can be used in Pharo, Squeak, etc.
Thanks
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
-- best, Eliot
-- Mariano http://marianopeck.wordpress.com
phew... done reading through overquoting :)
+1000 to removing tryXYZprimitive:
I was always wondering what those methods for, until i met a need to support debugging when invoking nativeboost-prim methods, because it needs special handing when invoking methods with native code via debugger.
Then i understood that this mechanism is necessary.. yet a bit awkward..
So, improving it is wellcome! :)
On Thu, Jan 26, 2012 at 11:18 AM, Igor Stasenko siguctua@gmail.com wrote:
phew... done reading through overquoting :)
+1000 to removing tryXYZprimitive:
I was always wondering what those methods for, until i met a need to support debugging when invoking nativeboost-prim methods, because it needs special handing when invoking methods with native code via debugger.
Then i understood that this mechanism is necessary.. yet a bit awkward..
Funny. Even after implementing the fix, I still do not understand why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
Thanks
So, improving it is wellcome! :)
-- Best regards, Igor Stasenko.
phew... done reading through overquoting :)
+1000 to removing tryXYZprimitive:
I was always wondering what those methods for, until i met a need to support debugging when invoking nativeboost-prim methods, because it needs special handing when invoking methods with native code via debugger.
Then i understood that this mechanism is necessary.. yet a bit awkward..
Funny. Even after implementing the fix, I still do not understand why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
Yes I want to understand too.
Thanks
So, improving it is wellcome! :)
-- Best regards, Igor Stasenko.
-- Mariano http://marianopeck.wordpress.com
On 26 January 2012 11:25, stephane ducasse stephane.ducasse@gmail.com wrote:
phew... done reading through overquoting :)
+1000 to removing tryXYZprimitive:
I was always wondering what those methods for, until i met a need to support debugging when invoking nativeboost-prim methods, because it needs special handing when invoking methods with native code via debugger.
Then i understood that this mechanism is necessary.. yet a bit awkward..
Funny. Even after implementing the fix, I still do not understand why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
Yes I want to understand too.
Here the hint for you: - what should happen when you doing 'step in' on method, which has a primitive?
Apparently, it should invoke that primitive , otherwise you will have difference between running and debugging modes, and will have different results, which makes debugger useless. So debugger should detect "somehow" if primitive was failed, and then step in into given method, or if its not, then step in = step over.
And these 'tryXYZ ... ' is exactly for solving this dilemma.
On Thu, Jan 26, 2012 at 11:38 AM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 11:25, stephane ducasse stephane.ducasse@gmail.com wrote:
phew... done reading through overquoting :)
+1000 to removing tryXYZprimitive:
I was always wondering what those methods for, until i met a need to support debugging when invoking nativeboost-prim methods, because it needs special handing when invoking methods with native code via debugger.
Then i understood that this mechanism is necessary.. yet a bit awkward..
Funny. Even after implementing the fix, I still do not understand why
all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
Yes I want to understand too.
Here the hint for you:
- what should happen when you doing 'step in' on method, which has a
primitive?
Apparently, it should invoke that primitive , otherwise you will have difference between running and debugging modes, and will have different results, which makes debugger useless. So debugger should detect "somehow" if primitive was failed, and then step in into given method, or if its not, then step in = step over.
And these 'tryXYZ ... ' is exactly for solving this dilemma.
Thanks Igor. So I wrote what I understood. Problem is that I always write for newbies (like me) so if it is too obvios or too long to put it as comment, let me know. I would appreaciate if someone can validate what I wrote. Today I will create a slice with mentioned solution + comments + removal of all those tryNamedPrimitive*.
----
"When using the debugger we want to run a method step by step. But what happens when we do a step into a CompiledMethod which has a primitive? If such a method is executed form outside the Debugger (normal scenario) the VMknows that such CompiledMethod has a primitive declaration and hence executes it. If it fails, then it continues executing all the bytecodes of the method. Otherwise, it just returns.
Now, what is the problem with the Debugger? The problem is that if the primitive fail, we don't want that the VM directly executes all the remaining bytecodes of the method. Instead, we would like to go step by step with he Debugger, just as happens with normal methods.
To solve the mentioned problem, we use the following trick: We have the original compiled method (the one that has a primitive invocation), the receiver and the arguments. So the idea is to use a template compiled method that ONLY contains the primitive declaration (it doesn't include all the original smalltalk code after the primitive). # tryNamedPrimitiveTemplateMethod answers such a template method which looks like:
tryNamedPrimitive <primitive:'to be set later' module:'to be ser later'> ^ ContextPart primitiveFailToken'
Since this method does not change its bytecodes for every invocation, we can reuse it for all methods with primitives. There are only 2 things we have to change in the template: the number of arguments and the primitive declaration (to use the correct primitive name and module name).
Then what we do is to run that compiled method with the receiver and arguments we have. The result is that we will be invoking almost the same original method but a slightly different version that does not have the smalltalk part after the primitive and that in contrast is sends # primitiveFailToken (which tells the Debugger what to do after). If this method invocation does not fail, then the Debugger continues debugging the sender of the primitive method. In this case, the step in is the same as step over. If the primitive fails, then the debugger continues executing the smalltalk part after the primitive method. In this case, step in is a real step in. "
-- Best regards, Igor Stasenko.
On 26 January 2012 14:42, Mariano Martinez Peck marianopeck@gmail.com wrote:
On Thu, Jan 26, 2012 at 11:38 AM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 11:25, stephane ducasse stephane.ducasse@gmail.com wrote:
phew... done reading through overquoting :)
+1000 to removing tryXYZprimitive:
I was always wondering what those methods for, until i met a need to support debugging when invoking nativeboost-prim methods, because it needs special handing when invoking methods with native code via debugger.
Then i understood that this mechanism is necessary.. yet a bit awkward..
Funny. Even after implementing the fix, I still do not understand why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
Yes I want to understand too.
Here the hint for you: - what should happen when you doing 'step in' on method, which has a primitive?
Apparently, it should invoke that primitive , otherwise you will have difference between running and debugging modes, and will have different results, which makes debugger useless. So debugger should detect "somehow" if primitive was failed, and then step in into given method, or if its not, then step in = step over.
And these 'tryXYZ ... ' is exactly for solving this dilemma.
Thanks Igor. So I wrote what I understood. Problem is that I always write for newbies (like me) so if it is too obvios or too long to put it as comment, let me know. I would appreaciate if someone can validate what I wrote. Today I will create a slice with mentioned solution + comments + removal of all those tryNamedPrimitive*.
"When using the debugger we want to run a method step by step. But what happens when we do a step into a CompiledMethod which has a primitive? If such a method is executed form outside the Debugger (normal scenario) the VM knows that such CompiledMethod has a primitive declaration and hence executes it. If it fails, then it continues executing all the bytecodes of the method. Otherwise, it just returns.
Now, what is the problem with the Debugger? The problem is that if the primitive fail, we don't want that the VM directly executes all the remaining bytecodes of the method. Instead, we would like to go step by step with he Debugger, just as happens with normal methods.
To solve the mentioned problem, we use the following trick: We have the original compiled method (the one that has a primitive invocation), the receiver and the arguments. So the idea is to use a template compiled method that ONLY contains the primitive declaration (it doesn't include all the original smalltalk code after the primitive). #tryNamedPrimitiveTemplateMethod answers such a template method which looks like:
tryNamedPrimitive <primitive:'to be set later' module:'to be ser later'> ^ ContextPart primitiveFailToken'
Since this method does not change its bytecodes for every invocation, we can reuse it for all methods with primitives. There are only 2 things we have to change in the template: the number of arguments and the primitive declaration (to use the correct primitive name and module name).
Then what we do is to run that compiled method with the receiver and arguments we have. The result is that we will be invoking almost the same original method but a slightly different version that does not have the smalltalk part after the primitive and that in contrast is sends #primitiveFailToken (which tells the Debugger what to do after). If this method invocation does not fail, then the Debugger continues debugging the sender of the primitive method. In this case, the step in is the same as step over. If the primitive fails, then the debugger continues executing the smalltalk part after the primitive method. In this case, step in is a real step in. "
There's another problem with using "tryXYZ" when primitive doing something weird with contexts (like block's #value) because then debugger cannot intercept switching contexts so easily, when primitive doing manipulation with contexts, because debugger have no idea where to put his next "break point" to do step-by-step evaluation.
I thinking that there should be special primitive which: - takes a context, a receiver, a method and arguments (or just a context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack) - invokes method's primitive - answers a nil if primitive failed or a new context object, which holds an updated context state after primitive possibly manipulated with context(s)
so, then debugger will use it like:
contextOrNil := self invokeMethodPrimitive: method context: currentContext receiver: recvr arguments: args contextOrNil ifNil: [ primitive failed .. ] ifNotNil: [ currentContext := contextOrNil ].
On Thu, Jan 26, 2012 at 3:00 PM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 14:42, Mariano Martinez Peck marianopeck@gmail.com wrote:
On Thu, Jan 26, 2012 at 11:38 AM, Igor Stasenko siguctua@gmail.com
wrote:
On 26 January 2012 11:25, stephane ducasse stephane.ducasse@gmail.com
wrote:
phew... done reading through overquoting :)
+1000 to removing tryXYZprimitive:
I was always wondering what those methods for, until i met a need to support debugging when invoking nativeboost-prim methods, because it needs special handing when invoking methods with native code via debugger.
Then i understood that this mechanism is necessary.. yet a bit
awkward..
Funny. Even after implementing the fix, I still do not understand
why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
Yes I want to understand too.
Here the hint for you:
- what should happen when you doing 'step in' on method, which has a
primitive?
Apparently, it should invoke that primitive , otherwise you will have difference between running and debugging modes, and will have different results, which makes debugger useless. So debugger should detect "somehow" if primitive was failed, and then step in into given method, or if its not, then step in = step over.
And these 'tryXYZ ... ' is exactly for solving this dilemma.
Thanks Igor. So I wrote what I understood. Problem is that I always
write for newbies (like me) so if it is too obvios or too long to put it as comment, let me know.
I would appreaciate if someone can validate what I wrote. Today I will create a slice with mentioned solution + comments + removal
of all those tryNamedPrimitive*.
"When using the debugger we want to run a method step by step. But what
happens when we do a step into a CompiledMethod which has a primitive? If such a method is executed form outside the Debugger (normal scenario) the VM knows that such CompiledMethod has a primitive declaration and hence executes it. If it fails, then it continues executing all the bytecodes of the method. Otherwise, it just returns.
Now, what is the problem with the Debugger? The problem is that if the
primitive fail, we don't want that the VM directly executes all the remaining bytecodes of the method. Instead, we would like to go step by step with he Debugger, just as happens with normal methods.
To solve the mentioned problem, we use the following trick: We have the
original compiled method (the one that has a primitive invocation), the receiver and the arguments. So the idea is to use a template compiled method that ONLY contains the primitive declaration (it doesn't include all the original smalltalk code after the primitive). #tryNamedPrimitiveTemplateMethod answers such a template method which looks like:
tryNamedPrimitive <primitive:'to be set later' module:'to be ser later'> ^ ContextPart primitiveFailToken'
Since this method does not change its bytecodes for every invocation,
we can reuse it for all methods with primitives. There are only 2 things we have to change in the template: the number of arguments and the primitive declaration (to use the correct primitive name and module name).
Then what we do is to run that compiled method with the receiver and
arguments we have. The result is that we will be invoking almost the same original method but a slightly different version that does not have the smalltalk part after the primitive and that in contrast is sends #primitiveFailToken (which tells the Debugger what to do after). If this method invocation does not fail, then the Debugger continues debugging the sender of the primitive method. In this case, the step in is the same as step over. If the primitive fails, then the debugger continues executing the smalltalk part after the primitive method. In this case, step in is a real step in. "
There's another problem with using "tryXYZ" when primitive doing something weird with contexts (like block's #value) because then debugger cannot intercept switching contexts so easily, when primitive doing manipulation with contexts, because debugger have no idea where to put his next "break point" to do step-by-step evaluation.
I thinking that there should be special primitive which:
- takes a context, a receiver, a method and arguments (or just a
context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack)
- invokes method's primitive
- answers a nil if primitive failed or a new context object, which
holds an updated context state after primitive possibly manipulated with context(s)
Isn't that what the new (only in Cog) primtiive 218 does ? #primitiveDoNamedPrimitiveWithArgs
Igor notice that all I have mentioned only happens when we are running in a non-cog VM because otherwise #primitiveDoNamedPrimitiveWithArgs will be used. See #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments
My code is just the fallback for none-code vms.
so, then debugger will use it like:
contextOrNil := self invokeMethodPrimitive: method context: currentContext receiver: recvr arguments: args contextOrNil ifNil: [ primitive failed .. ] ifNotNil: [ currentContext := contextOrNil ].
-- Best regards, Igor Stasenko.
On 26 January 2012 15:08, Mariano Martinez Peck marianopeck@gmail.com wrote:
There's another problem with using "tryXYZ" when primitive doing something weird with contexts (like block's #value) because then debugger cannot intercept switching contexts so easily, when primitive doing manipulation with contexts, because debugger have no idea where to put his next "break point" to do step-by-step evaluation.
I thinking that there should be special primitive which: - takes a context, a receiver, a method and arguments (or just a context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack) - invokes method's primitive - answers a nil if primitive failed or a new context object, which holds an updated context state after primitive possibly manipulated with context(s)
Isn't that what the new (only in Cog) primtiive 218 does ? #primitiveDoNamedPrimitiveWithArgs
i just checked the code. it invokes a prim. but no, if prim manipulate the context state, or just changes an active context, like in (sema wait) a debugger won't be able to intercept return from prim, and an arbitrary code will go running for indefinite period, without any guarantees that it will ever return back to debugger somehow.
And this is not acceptable.
if you look at #doPrimitive: primitiveIndex method: meth receiver: receiver args: arguments there a huge case statement, which actually dealing with all those prims which manipulating the contexts, now if primitiveDoNamedPrimitiveWithArgs would care to answer an updated debuggable context state , that method will turn into 1-liner. And what i don't like about that code, that image kind of having an intimate knowledge about what certain primitive(s) doing. It ties closely VM implementation details with image, which is not good.
So, IMO, a right way to implement a "tryPrimitive" primitive is to pass a single argument to it: debuggable context. and it should return an updated debuggable context or nil if primitive failed.
Igor notice that all I have mentioned only happens when we are running in a non-cog VM because otherwise #primitiveDoNamedPrimitiveWithArgs will be used. See #tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments
My code is just the fallback for none-code vms.
so, then debugger will use it like:
contextOrNil := self invokeMethodPrimitive: method context: currentContext receiver: recvr arguments: args contextOrNil ifNil: [ primitive failed .. ] ifNotNil: [ currentContext := contextOrNil ].
-- Best regards, Igor Stasenko.
-- Mariano http://marianopeck.wordpress.com
On Thu, Jan 26, 2012 at 6:00 AM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 14:42, Mariano Martinez Peck marianopeck@gmail.com wrote:
On Thu, Jan 26, 2012 at 11:38 AM, Igor Stasenko siguctua@gmail.com
wrote:
On 26 January 2012 11:25, stephane ducasse stephane.ducasse@gmail.com
wrote:
phew... done reading through overquoting :)
+1000 to removing tryXYZprimitive:
I was always wondering what those methods for, until i met a need to support debugging when invoking nativeboost-prim methods, because it needs special handing when invoking methods with native code via debugger.
Then i understood that this mechanism is necessary.. yet a bit
awkward..
Funny. Even after implementing the fix, I still do not understand
why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
Yes I want to understand too.
Here the hint for you:
- what should happen when you doing 'step in' on method, which has a
primitive?
Apparently, it should invoke that primitive , otherwise you will have difference between running and debugging modes, and will have different results, which makes debugger useless. So debugger should detect "somehow" if primitive was failed, and then step in into given method, or if its not, then step in = step over.
And these 'tryXYZ ... ' is exactly for solving this dilemma.
Thanks Igor. So I wrote what I understood. Problem is that I always
write for newbies (like me) so if it is too obvios or too long to put it as comment, let me know.
I would appreaciate if someone can validate what I wrote. Today I will create a slice with mentioned solution + comments + removal
of all those tryNamedPrimitive*.
"When using the debugger we want to run a method step by step. But what
happens when we do a step into a CompiledMethod which has a primitive? If such a method is executed form outside the Debugger (normal scenario) the VM knows that such CompiledMethod has a primitive declaration and hence executes it. If it fails, then it continues executing all the bytecodes of the method. Otherwise, it just returns.
Now, what is the problem with the Debugger? The problem is that if the
primitive fail, we don't want that the VM directly executes all the remaining bytecodes of the method. Instead, we would like to go step by step with he Debugger, just as happens with normal methods.
To solve the mentioned problem, we use the following trick: We have the
original compiled method (the one that has a primitive invocation), the receiver and the arguments. So the idea is to use a template compiled method that ONLY contains the primitive declaration (it doesn't include all the original smalltalk code after the primitive). #tryNamedPrimitiveTemplateMethod answers such a template method which looks like:
tryNamedPrimitive <primitive:'to be set later' module:'to be ser later'> ^ ContextPart primitiveFailToken'
Since this method does not change its bytecodes for every invocation,
we can reuse it for all methods with primitives. There are only 2 things we have to change in the template: the number of arguments and the primitive declaration (to use the correct primitive name and module name).
Then what we do is to run that compiled method with the receiver and
arguments we have. The result is that we will be invoking almost the same original method but a slightly different version that does not have the smalltalk part after the primitive and that in contrast is sends #primitiveFailToken (which tells the Debugger what to do after). If this method invocation does not fail, then the Debugger continues debugging the sender of the primitive method. In this case, the step in is the same as step over. If the primitive fails, then the debugger continues executing the smalltalk part after the primitive method. In this case, step in is a real step in. "
There's another problem with using "tryXYZ" when primitive doing something weird with contexts (like block's #value) because then debugger cannot intercept switching contexts so easily, when primitive doing manipulation with contexts, because debugger have no idea where to put his next "break point" to do step-by-step evaluation.
I thinking that there should be special primitive which:
- takes a context, a receiver, a method and arguments (or just a
context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack)
- invokes method's primitive
- answers a nil if primitive failed or a new context object, which
holds an updated context state after primitive possibly manipulated with context(s)
No, not needed. Manipulation of contexts does not require primitives since contexts are first-class, and the effect of these primitives can be simulated in Smalltalk (just as the effect of executing bytecodes can be simulated). Look at the caller of tryPrimitive:withArgs: and tryNamedPrimitiveIn:for:withArgs:, namely ContextPart>doPrimitive:method:receiver:args:. It handles all these primitives such as perform:[*], value[:value:*], withArgs:executeMethod:.
so, then debugger will use it like:
contextOrNil := self invokeMethodPrimitive: method context: currentContext receiver: recvr arguments: args contextOrNil ifNil: [ primitive failed .. ] ifNotNil: [ currentContext := contextOrNil ].
-- Best regards, Igor Stasenko.
Well. My final proposal is in: http://code.google.com/p/pharo/issues/detail?id=5223 With comments and some small refactors. As said, I would love if someone can take a view before integrating.
Cheers
On Thu, Jan 26, 2012 at 7:25 PM, Eliot Miranda eliot.miranda@gmail.comwrote:
On Thu, Jan 26, 2012 at 6:00 AM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 14:42, Mariano Martinez Peck marianopeck@gmail.com wrote:
On Thu, Jan 26, 2012 at 11:38 AM, Igor Stasenko siguctua@gmail.com
wrote:
On 26 January 2012 11:25, stephane ducasse stephane.ducasse@gmail.com
wrote:
phew... done reading through overquoting :)
+1000 to removing tryXYZprimitive:
I was always wondering what those methods for, until i met a need to support debugging when invoking nativeboost-prim methods, because it needs special handing when invoking methods with native code via debugger.
Then i understood that this mechanism is necessary.. yet a bit
awkward..
Funny. Even after implementing the fix, I still do not understand
why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
Yes I want to understand too.
Here the hint for you:
- what should happen when you doing 'step in' on method, which has a
primitive?
Apparently, it should invoke that primitive , otherwise you will have difference between running and debugging modes, and will have different results, which makes debugger useless. So debugger should detect "somehow" if primitive was failed, and then step in into given method, or if its not, then step in = step over.
And these 'tryXYZ ... ' is exactly for solving this dilemma.
Thanks Igor. So I wrote what I understood. Problem is that I always
write for newbies (like me) so if it is too obvios or too long to put it as comment, let me know.
I would appreaciate if someone can validate what I wrote. Today I will create a slice with mentioned solution + comments +
removal of all those tryNamedPrimitive*.
"When using the debugger we want to run a method step by step. But what
happens when we do a step into a CompiledMethod which has a primitive? If such a method is executed form outside the Debugger (normal scenario) the VM knows that such CompiledMethod has a primitive declaration and hence executes it. If it fails, then it continues executing all the bytecodes of the method. Otherwise, it just returns.
Now, what is the problem with the Debugger? The problem is that if the
primitive fail, we don't want that the VM directly executes all the remaining bytecodes of the method. Instead, we would like to go step by step with he Debugger, just as happens with normal methods.
To solve the mentioned problem, we use the following trick: We have the
original compiled method (the one that has a primitive invocation), the receiver and the arguments. So the idea is to use a template compiled method that ONLY contains the primitive declaration (it doesn't include all the original smalltalk code after the primitive). #tryNamedPrimitiveTemplateMethod answers such a template method which looks like:
tryNamedPrimitive <primitive:'to be set later' module:'to be ser later'> ^ ContextPart primitiveFailToken'
Since this method does not change its bytecodes for every invocation,
we can reuse it for all methods with primitives. There are only 2 things we have to change in the template: the number of arguments and the primitive declaration (to use the correct primitive name and module name).
Then what we do is to run that compiled method with the receiver and
arguments we have. The result is that we will be invoking almost the same original method but a slightly different version that does not have the smalltalk part after the primitive and that in contrast is sends #primitiveFailToken (which tells the Debugger what to do after). If this method invocation does not fail, then the Debugger continues debugging the sender of the primitive method. In this case, the step in is the same as step over. If the primitive fails, then the debugger continues executing the smalltalk part after the primitive method. In this case, step in is a real step in. "
There's another problem with using "tryXYZ" when primitive doing something weird with contexts (like block's #value) because then debugger cannot intercept switching contexts so easily, when primitive doing manipulation with contexts, because debugger have no idea where to put his next "break point" to do step-by-step evaluation.
I thinking that there should be special primitive which:
- takes a context, a receiver, a method and arguments (or just a
context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack)
- invokes method's primitive
- answers a nil if primitive failed or a new context object, which
holds an updated context state after primitive possibly manipulated with context(s)
No, not needed. Manipulation of contexts does not require primitives since contexts are first-class, and the effect of these primitives can be simulated in Smalltalk (just as the effect of executing bytecodes can be simulated). Look at the caller of tryPrimitive:withArgs: and tryNamedPrimitiveIn:for:withArgs:, namely ContextPart>doPrimitive:method:receiver:args:. It handles all these primitives such as perform:[*], value[:value:*], withArgs:executeMethod:.
so, then debugger will use it like:
contextOrNil := self invokeMethodPrimitive: method context: currentContext receiver: recvr arguments: args contextOrNil ifNil: [ primitive failed .. ] ifNotNil: [ currentContext := contextOrNil ].
-- Best regards, Igor Stasenko.
-- best, Eliot
do step-by-step evaluation.
I thinking that there should be special primitive which:
- takes a context, a receiver, a method and arguments (or just a
context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack)
- invokes method's primitive
- answers a nil if primitive failed or a new context object, which
holds an updated context state after primitive possibly manipulated with context(s)
No, not needed. Manipulation of contexts does not require primitives since contexts are first-class, and the effect of these primitives can be simulated in Smalltalk (just as the effect of executing bytecodes can be simulated). Look at the caller of tryPrimitive:withArgs: and tryNamedPrimitiveIn:for:withArgs:, namely ContextPart>doPrimitive:method:receiver:args:. It handles all these primitives such as perform:[*], value[:value:*], withArgs:executeMethod:.
Eliot I think that you misunderstood what and why igor is saying that. He knows pretty well
doPrimitive:method:receiver:args:. I imagine that he will reply.
Stef
On 26 January 2012 19:25, Eliot Miranda eliot.miranda@gmail.com wrote:
On Thu, Jan 26, 2012 at 6:00 AM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 14:42, Mariano Martinez Peck marianopeck@gmail.com wrote:
On Thu, Jan 26, 2012 at 11:38 AM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 11:25, stephane ducasse stephane.ducasse@gmail.com wrote:
phew... done reading through overquoting :)
+1000 to removing tryXYZprimitive:
I was always wondering what those methods for, until i met a need to support debugging when invoking nativeboost-prim methods, because it needs special handing when invoking methods with native code via debugger.
Then i understood that this mechanism is necessary.. yet a bit awkward..
Funny. Even after implementing the fix, I still do not understand why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
Yes I want to understand too.
Here the hint for you: - what should happen when you doing 'step in' on method, which has a primitive?
Apparently, it should invoke that primitive , otherwise you will have difference between running and debugging modes, and will have different results, which makes debugger useless. So debugger should detect "somehow" if primitive was failed, and then step in into given method, or if its not, then step in = step over.
And these 'tryXYZ ... ' is exactly for solving this dilemma.
Thanks Igor. So I wrote what I understood. Problem is that I always write for newbies (like me) so if it is too obvios or too long to put it as comment, let me know. I would appreaciate if someone can validate what I wrote. Today I will create a slice with mentioned solution + comments + removal of all those tryNamedPrimitive*.
"When using the debugger we want to run a method step by step. But what happens when we do a step into a CompiledMethod which has a primitive? If such a method is executed form outside the Debugger (normal scenario) the VM knows that such CompiledMethod has a primitive declaration and hence executes it. If it fails, then it continues executing all the bytecodes of the method. Otherwise, it just returns.
Now, what is the problem with the Debugger? The problem is that if the primitive fail, we don't want that the VM directly executes all the remaining bytecodes of the method. Instead, we would like to go step by step with he Debugger, just as happens with normal methods.
To solve the mentioned problem, we use the following trick: We have the original compiled method (the one that has a primitive invocation), the receiver and the arguments. So the idea is to use a template compiled method that ONLY contains the primitive declaration (it doesn't include all the original smalltalk code after the primitive). #tryNamedPrimitiveTemplateMethod answers such a template method which looks like:
tryNamedPrimitive <primitive:'to be set later' module:'to be ser later'> ^ ContextPart primitiveFailToken'
Since this method does not change its bytecodes for every invocation, we can reuse it for all methods with primitives. There are only 2 things we have to change in the template: the number of arguments and the primitive declaration (to use the correct primitive name and module name).
Then what we do is to run that compiled method with the receiver and arguments we have. The result is that we will be invoking almost the same original method but a slightly different version that does not have the smalltalk part after the primitive and that in contrast is sends #primitiveFailToken (which tells the Debugger what to do after). If this method invocation does not fail, then the Debugger continues debugging the sender of the primitive method. In this case, the step in is the same as step over. If the primitive fails, then the debugger continues executing the smalltalk part after the primitive method. In this case, step in is a real step in. "
There's another problem with using "tryXYZ" when primitive doing something weird with contexts (like block's #value) because then debugger cannot intercept switching contexts so easily, when primitive doing manipulation with contexts, because debugger have no idea where to put his next "break point" to do step-by-step evaluation.
I thinking that there should be special primitive which: - takes a context, a receiver, a method and arguments (or just a context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack) - invokes method's primitive - answers a nil if primitive failed or a new context object, which holds an updated context state after primitive possibly manipulated with context(s)
No, not needed. Manipulation of contexts does not require primitives since contexts are first-class, and the effect of these primitives can be simulated in Smalltalk (just as the effect of executing bytecodes can be simulated). Look at the caller of tryPrimitive:withArgs: and tryNamedPrimitiveIn:for:withArgs:, namely ContextPart>doPrimitive:method:receiver:args:. It handles all these primitives such as perform:[*], value[:value:*], withArgs:executeMethod:.
Looks like you don't read carefully. Exactly because of looking to ContextPart>doPrimitive:method:receiver:args: i proposed another way. And besides it is much simpler. If tomorrow we introduce new primitive which manipulates context, you will need to fix this method, because it duplicating knowledge of what VM does.
On Fri, Jan 27, 2012 at 5:55 AM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 19:25, Eliot Miranda eliot.miranda@gmail.com wrote:
On Thu, Jan 26, 2012 at 6:00 AM, Igor Stasenko siguctua@gmail.com
wrote:
On 26 January 2012 14:42, Mariano Martinez Peck marianopeck@gmail.com
wrote:
On Thu, Jan 26, 2012 at 11:38 AM, Igor Stasenko siguctua@gmail.com
wrote:
On 26 January 2012 11:25, stephane ducasse <
stephane.ducasse@gmail.com> wrote:
> phew... done reading through overquoting :) > > +1000 to removing tryXYZprimitive: > > I was always wondering what those methods for, until i met a need
to
> support debugging when invoking nativeboost-prim methods, > because it needs special handing when invoking methods with native > code via debugger. > > Then i understood that this mechanism is necessary.. yet a bit
awkward..
> > Funny. Even after implementing the fix, I still do not understand
why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
Yes I want to understand too.
Here the hint for you:
- what should happen when you doing 'step in' on method, which has
a primitive?
Apparently, it should invoke that primitive , otherwise you will have difference between running and debugging modes, and will have different results, which makes debugger useless. So debugger should detect "somehow" if primitive was failed, and then step in into given method, or if its not, then step in = step over.
And these 'tryXYZ ... ' is exactly for solving this dilemma.
Thanks Igor. So I wrote what I understood. Problem is that I always
write for newbies (like me) so if it is too obvios or too long to put it as comment, let me know.
I would appreaciate if someone can validate what I wrote. Today I will create a slice with mentioned solution + comments +
removal of all those tryNamedPrimitive*.
"When using the debugger we want to run a method step by step. But
what happens when we do a step into a CompiledMethod which has a primitive? If such a method is executed form outside the Debugger (normal scenario) the VM knows that such CompiledMethod has a primitive declaration and hence executes it. If it fails, then it continues executing all the bytecodes of the method. Otherwise, it just returns.
Now, what is the problem with the Debugger? The problem is that if
the primitive fail, we don't want that the VM directly executes all the remaining bytecodes of the method. Instead, we would like to go step by step with he Debugger, just as happens with normal methods.
To solve the mentioned problem, we use the following trick: We have
the original compiled method (the one that has a primitive invocation), the receiver and the arguments. So the idea is to use a template compiled method that ONLY contains the primitive declaration (it doesn't include all the original smalltalk code after the primitive). #tryNamedPrimitiveTemplateMethod answers such a template method which looks like:
tryNamedPrimitive <primitive:'to be set later' module:'to be ser later'> ^ ContextPart primitiveFailToken'
Since this method does not change its bytecodes for every
invocation, we can reuse it for all methods with primitives. There are only 2 things we have to change in the template: the number of arguments and the primitive declaration (to use the correct primitive name and module name).
Then what we do is to run that compiled method with the receiver and
arguments we have. The result is that we will be invoking almost the same original method but a slightly different version that does not have the smalltalk part after the primitive and that in contrast is sends #primitiveFailToken (which tells the Debugger what to do after). If this method invocation does not fail, then the Debugger continues debugging the sender of the primitive method. In this case, the step in is the same as step over. If the primitive fails, then the debugger continues executing the smalltalk part after the primitive method. In this case, step in is a real step in. "
There's another problem with using "tryXYZ" when primitive doing something weird with contexts (like block's #value) because then debugger cannot intercept switching contexts so easily, when primitive doing manipulation with contexts, because debugger have no idea where to put his next "break point" to do step-by-step evaluation.
I thinking that there should be special primitive which:
- takes a context, a receiver, a method and arguments (or just a
context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack)
- invokes method's primitive
- answers a nil if primitive failed or a new context object, which
holds an updated context state after primitive possibly manipulated with context(s)
No, not needed. Manipulation of contexts does not require primitives
since contexts are first-class, and the effect of these primitives can be simulated in Smalltalk (just as the effect of executing bytecodes can be simulated). Look at the caller of tryPrimitive:withArgs: and tryNamedPrimitiveIn:for:withArgs:, namely ContextPart>doPrimitive:method:receiver:args:. It handles all these primitives such as perform:[*], value[:value:*], withArgs:executeMethod:.
Looks like you don't read carefully. Exactly because of looking to ContextPart>doPrimitive:method:receiver:args: i proposed another way. And besides it is much simpler. If tomorrow we introduce new primitive which manipulates context, you will need to fix this method, because it duplicating knowledge of what VM does.
I disagree. ContextPart already duplicates the VM. The direction you propose ends up putting all context functionality in the VM, which makes the system harder to understand (the SMalltalk code is more readable), harder to experiment with (one can no longer implement experiments in the image, for example as I did in implementing the closure compiler), and harder to implement (implementing and testing tricky do-lots-of-things primitives in the VM is harder than implementing and testing their equivalents in the image). So I think this primitive is a really bad idea. Its going to be much more complex than doPrimitive:method:receiver:args:.
-- Best regards, Igor Stasenko.
On Fri, Jan 27, 2012 at 9:56 AM, Eliot Miranda eliot.miranda@gmail.comwrote:
On Fri, Jan 27, 2012 at 5:55 AM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 19:25, Eliot Miranda eliot.miranda@gmail.com wrote:
On Thu, Jan 26, 2012 at 6:00 AM, Igor Stasenko siguctua@gmail.com
wrote:
On 26 January 2012 14:42, Mariano Martinez Peck marianopeck@gmail.com
wrote:
On Thu, Jan 26, 2012 at 11:38 AM, Igor Stasenko siguctua@gmail.com
wrote:
On 26 January 2012 11:25, stephane ducasse <
stephane.ducasse@gmail.com> wrote:
> >> phew... done reading through overquoting :) >> >> +1000 to removing tryXYZprimitive: >> >> I was always wondering what those methods for, until i met a
need to
>> support debugging when invoking nativeboost-prim methods, >> because it needs special handing when invoking methods with
native
>> code via debugger. >> >> Then i understood that this mechanism is necessary.. yet a bit
awkward..
>> >> Funny. Even after implementing the fix, I still do not
understand why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
> > Yes I want to understand too. >
Here the hint for you:
- what should happen when you doing 'step in' on method, which has
a primitive?
Apparently, it should invoke that primitive , otherwise you will
have
difference between running and debugging modes, and will have different results, which makes debugger useless. So debugger should detect "somehow" if primitive was failed, and
then
step in into given method, or if its not, then step in = step over.
And these 'tryXYZ ... ' is exactly for solving this dilemma.
Thanks Igor. So I wrote what I understood. Problem is that I always
write for newbies (like me) so if it is too obvios or too long to put it as comment, let me know.
I would appreaciate if someone can validate what I wrote. Today I will create a slice with mentioned solution + comments +
removal of all those tryNamedPrimitive*.
"When using the debugger we want to run a method step by step. But
what happens when we do a step into a CompiledMethod which has a primitive? If such a method is executed form outside the Debugger (normal scenario) the VM knows that such CompiledMethod has a primitive declaration and hence executes it. If it fails, then it continues executing all the bytecodes of the method. Otherwise, it just returns.
Now, what is the problem with the Debugger? The problem is that if
the primitive fail, we don't want that the VM directly executes all the remaining bytecodes of the method. Instead, we would like to go step by step with he Debugger, just as happens with normal methods.
To solve the mentioned problem, we use the following trick: We have
the original compiled method (the one that has a primitive invocation), the receiver and the arguments. So the idea is to use a template compiled method that ONLY contains the primitive declaration (it doesn't include all the original smalltalk code after the primitive). #tryNamedPrimitiveTemplateMethod answers such a template method which looks like:
tryNamedPrimitive <primitive:'to be set later' module:'to be ser later'> ^ ContextPart primitiveFailToken'
Since this method does not change its bytecodes for every
invocation, we can reuse it for all methods with primitives. There are only 2 things we have to change in the template: the number of arguments and the primitive declaration (to use the correct primitive name and module name).
Then what we do is to run that compiled method with the receiver and
arguments we have. The result is that we will be invoking almost the same original method but a slightly different version that does not have the smalltalk part after the primitive and that in contrast is sends #primitiveFailToken (which tells the Debugger what to do after). If this method invocation does not fail, then the Debugger continues debugging the sender of the primitive method. In this case, the step in is the same as step over. If the primitive fails, then the debugger continues executing the smalltalk part after the primitive method. In this case, step in is a real step in. "
There's another problem with using "tryXYZ" when primitive doing something weird with contexts (like block's #value) because then debugger cannot intercept switching contexts so easily, when primitive doing manipulation with contexts, because debugger have no idea where to put his next "break point" to do step-by-step evaluation.
I thinking that there should be special primitive which:
- takes a context, a receiver, a method and arguments (or just a
context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack)
- invokes method's primitive
- answers a nil if primitive failed or a new context object, which
holds an updated context state after primitive possibly manipulated with context(s)
No, not needed. Manipulation of contexts does not require primitives
since contexts are first-class, and the effect of these primitives can be simulated in Smalltalk (just as the effect of executing bytecodes can be simulated). Look at the caller of tryPrimitive:withArgs: and tryNamedPrimitiveIn:for:withArgs:, namely ContextPart>doPrimitive:method:receiver:args:. It handles all these primitives such as perform:[*], value[:value:*], withArgs:executeMethod:.
Looks like you don't read carefully. Exactly because of looking to ContextPart>doPrimitive:method:receiver:args: i proposed another way. And besides it is much simpler. If tomorrow we introduce new primitive which manipulates context, you will need to fix this method, because it duplicating knowledge of what VM does.
I disagree. ContextPart already duplicates the VM. The direction you propose ends up putting all context functionality in the VM, which makes the system harder to understand (the SMalltalk code is more readable), harder to experiment with (one can no longer implement experiments in the image, for example as I did in implementing the closure compiler), and harder to implement (implementing and testing tricky do-lots-of-things primitives in the VM is harder than implementing and testing their equivalents in the image). So I think this primitive is a really bad idea. Its going to be much more complex than doPrimitive:method:receiver:args:.
And of course experience shows it is much quicker and easier to distribute Smalltalk improvements than new virtual machines, so pushing important functionality down into the VM is a retrograde step. It seems out of keeping with NativeBoost and ideas such as moving the linking machinery for named primitives and the FFI up into the image, decomposing them into a more general set of smaller simpler primitives.
-- Best regards, Igor Stasenko.
-- best, Eliot
On 27 January 2012 19:40, Eliot Miranda eliot.miranda@gmail.com wrote:
On Fri, Jan 27, 2012 at 9:56 AM, Eliot Miranda eliot.miranda@gmail.com wrote:
On Fri, Jan 27, 2012 at 5:55 AM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 19:25, Eliot Miranda eliot.miranda@gmail.com wrote:
On Thu, Jan 26, 2012 at 6:00 AM, Igor Stasenko siguctua@gmail.com wrote:
On 26 January 2012 14:42, Mariano Martinez Peck marianopeck@gmail.com wrote:
On Thu, Jan 26, 2012 at 11:38 AM, Igor Stasenko siguctua@gmail.com wrote: > > > On 26 January 2012 11:25, stephane ducasse stephane.ducasse@gmail.com wrote: > > > >> phew... done reading through overquoting :) > >> > >> +1000 to removing tryXYZprimitive: > >> > >> I was always wondering what those methods for, until i met a need to > >> support debugging when invoking nativeboost-prim methods, > >> because it needs special handing when invoking methods with native > >> code via debugger. > >> > >> Then i understood that this mechanism is necessary.. yet a bit awkward.. > >> > >> Funny. Even after implementing the fix, I still do not understand why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code) > > > > Yes I want to understand too. > > > > Here the hint for you: > - what should happen when you doing 'step in' on method, which has a primitive? > > Apparently, it should invoke that primitive , otherwise you will have > difference between running and debugging modes, > and will have different results, which makes debugger useless. > So debugger should detect "somehow" if primitive was failed, and then > step in into given method, > or if its not, then step in = step over. > > And these 'tryXYZ ... ' is exactly for solving this dilemma.
Thanks Igor. So I wrote what I understood. Problem is that I always write for newbies (like me) so if it is too obvios or too long to put it as comment, let me know. I would appreaciate if someone can validate what I wrote. Today I will create a slice with mentioned solution + comments + removal of all those tryNamedPrimitive*.
"When using the debugger we want to run a method step by step. But what happens when we do a step into a CompiledMethod which has a primitive? If such a method is executed form outside the Debugger (normal scenario) the VM knows that such CompiledMethod has a primitive declaration and hence executes it. If it fails, then it continues executing all the bytecodes of the method. Otherwise, it just returns.
Now, what is the problem with the Debugger? The problem is that if the primitive fail, we don't want that the VM directly executes all the remaining bytecodes of the method. Instead, we would like to go step by step with he Debugger, just as happens with normal methods.
To solve the mentioned problem, we use the following trick: We have the original compiled method (the one that has a primitive invocation), the receiver and the arguments. So the idea is to use a template compiled method that ONLY contains the primitive declaration (it doesn't include all the original smalltalk code after the primitive). #tryNamedPrimitiveTemplateMethod answers such a template method which looks like:
tryNamedPrimitive <primitive:'to be set later' module:'to be ser later'> ^ ContextPart primitiveFailToken'
Since this method does not change its bytecodes for every invocation, we can reuse it for all methods with primitives. There are only 2 things we have to change in the template: the number of arguments and the primitive declaration (to use the correct primitive name and module name).
Then what we do is to run that compiled method with the receiver and arguments we have. The result is that we will be invoking almost the same original method but a slightly different version that does not have the smalltalk part after the primitive and that in contrast is sends #primitiveFailToken (which tells the Debugger what to do after). If this method invocation does not fail, then the Debugger continues debugging the sender of the primitive method. In this case, the step in is the same as step over. If the primitive fails, then the debugger continues executing the smalltalk part after the primitive method. In this case, step in is a real step in. "
There's another problem with using "tryXYZ" when primitive doing something weird with contexts (like block's #value) because then debugger cannot intercept switching contexts so easily, when primitive doing manipulation with contexts, because debugger have no idea where to put his next "break point" to do step-by-step evaluation.
I thinking that there should be special primitive which: - takes a context, a receiver, a method and arguments (or just a context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack) - invokes method's primitive - answers a nil if primitive failed or a new context object, which holds an updated context state after primitive possibly manipulated with context(s)
No, not needed. Manipulation of contexts does not require primitives since contexts are first-class, and the effect of these primitives can be simulated in Smalltalk (just as the effect of executing bytecodes can be simulated). Look at the caller of tryPrimitive:withArgs: and tryNamedPrimitiveIn:for:withArgs:, namely ContextPart>doPrimitive:method:receiver:args:. It handles all these primitives such as perform:[*], value[:value:*], withArgs:executeMethod:.
Looks like you don't read carefully. Exactly because of looking to ContextPart>doPrimitive:method:receiver:args: i proposed another way. And besides it is much simpler. If tomorrow we introduce new primitive which manipulates context, you will need to fix this method, because it duplicating knowledge of what VM does.
I disagree. ContextPart already duplicates the VM. The direction you propose ends up putting all context functionality in the VM, which makes the system harder to understand (the SMalltalk code is more readable), harder to experiment with (one can no longer implement experiments in the image, for example as I did in implementing the closure compiler), and harder to implement (implementing and testing tricky do-lots-of-things primitives in the VM is harder than implementing and testing their equivalents in the image). So I think this primitive is a really bad idea. Its going to be much more complex than doPrimitive:method:receiver:args:.
Hmm.. are you sure about much complex? As to me it is fairly simple:
1. remember current context (debugger) 2. activate a debuggable context (passed as argument) 3. invoke a primitive 4. activate a debugger context back and return a modified debuggable context as result.
And of course experience shows it is much quicker and easier to distribute Smalltalk improvements than new virtual machines, so pushing important functionality down into the VM is a retrograde step. It seems out of keeping with NativeBoost and ideas such as moving the linking machinery for named primitives and the FFI up into the image, decomposing them into a more general set of smaller simpler primitives.
This is true. But exactly because of it, we need such prim! Think, how you going to debug a dynamically generated code, which manipulates a contexts, if by invoking it, your debugger will immediately lose a control?
A simulation is not an answer here, because you actually want to run your your dynamically generated code and check if it behaves well, while if you just simulate it you will never know if its ok, because then you will be testing whether your simulation behaves correctly, instead of real thing. And of course, then every time you changing the native implementation, you will also need to update a simulation code as well -> a best way to kill a project, when two independent parts must behave identically, but since a lazy developers forgetting keep them in sync, it falling apart.
This is actually a situation which we having now, because implementation of #doPrimitive: primitiveIndex method: meth receiver: receiver args: arguments is based on assumption that VM are in sync with image, and every time you change something, you have to go there and sync stuff. Why we cannot follow DRY principle?
So, without having it, i had to introduce an awful hack in NativeBoost, an override in ContextPart package, because i have no choice:
ContextPart>>tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments "Hack. Attempt to execute the named primitive from the given compiled method" | selector theMethod spec | (NativeBoost isNativeMethod: aCompiledMethod) ifTrue: [ ^ NativeBoost tryRunNativeCode: aCompiledMethod for: aReceiver withArgs: arguments onFail: [ ^ PrimitiveFailToken ]. ]. arguments size > 8 ifTrue:[^PrimitiveFailToken]. selector := #( tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with: tryNamedPrimitive:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. theMethod := aReceiver class lookupSelector: selector. theMethod == nil ifTrue:[^PrimitiveFailToken]. spec := theMethod literalAt: 1. spec replaceFrom: 1 to: spec size with: (aCompiledMethod literalAt: 1) startingAt: 1. theMethod flushCache. selector flushCache. ^aReceiver perform: selector withArguments: arguments
--------------- NativeBoost class>>tryRunNativeCode: aCompiledMethod for: aReceiver withArgs: arguments onFail: aFailBlock "run only a native code of given method, and if it fails evaluate aFailBlock instead" | src node m failObj result | self assert: (self isNativeMethod: aCompiledMethod). src := String streamContents: [:str | str nextPutAll: 'NBDebug_stub'. 1 to: aCompiledMethod numArgs do: [:i | i = 1 ifTrue: [ str nextPutAll: ': ' ] ifFalse: [ str nextPutAll: ' with: ' ]. str nextPutAll: 'arg'. i printOn: str. ]. str cr; nextPutAll: ' <primitive: #primitiveNativeCall module: #NativeBoostPlugin >'; cr; nextPutAll: ' ^ ''theFailLiteral'' '. ].
node := Compiler new compile: src in: aCompiledMethod methodClass classified: nil notifying: nil ifFail: [ ^ aFailBlock value ].
m := node generate: aCompiledMethod trailer. self assert: (m literalAt: 2) = 'theFailLiteral'. failObj := Object new. m literalAt: 2 put: failObj. m flushCache. m selector flushCache. result := m valueWithReceiver: aReceiver arguments: arguments. ^ result == failObj ifTrue: [ aFailBlock value] ifFalse: [ result ] and as you can see, an implementation of "please simply run just the primitive of method" at language side is not looking nice and easy. And of course, if by chance , a native code manipulate context, then the above hack not going to work anymore. This code actually assumes that primitive plays nicely and not doing such nasty things :)
Another reason why we need such prim: have you tried to debug a debugger? You can't , because there is a simulation guard:
<primitive: 19> "Simulation guard" "If successful, push result and return resuming context, else ^ PrimitiveFailToken" (primitiveIndex = 19) ifTrue: [Smalltalk tools debugContext: self label:'Code simulation error' contents: nil].
This is because you cannot instruct VM that the code you stepping over should not escape out of your control.
On 1/28/2012 23:09, Igor Stasenko wrote:
Another reason why we need such prim: have you tried to debug a debugger? You can't , because there is a simulation guard:
<primitive: 19> "Simulation guard" "If successful, push result and return resuming context, else ^ PrimitiveFailToken" (primitiveIndex = 19) ifTrue: [Smalltalk tools debugContext: self label:'Code simulation error' contents: nil].
This is because you cannot instruct VM that the code you stepping over should not escape out of your control.
Sorry, but that's not what the simulation guard is for. Historically the VM would just crash when trying to simulate certain methods (like the above). So I put in simulation guards to prevent VM crashes. If the VM can simulate these methods, then you can just take them out.
Cheers, - Andreas
On Sat, Jan 28, 2012 at 12:52 PM, Igor Stasenko siguctua@gmail.com wrote:
On 27 January 2012 19:40, Eliot Miranda eliot.miranda@gmail.com wrote:
On Fri, Jan 27, 2012 at 9:56 AM, Eliot Miranda eliot.miranda@gmail.com
wrote:
On Fri, Jan 27, 2012 at 5:55 AM, Igor Stasenko siguctua@gmail.com
wrote:
On 26 January 2012 19:25, Eliot Miranda eliot.miranda@gmail.com
wrote:
On Thu, Jan 26, 2012 at 6:00 AM, Igor Stasenko siguctua@gmail.com
wrote:
On 26 January 2012 14:42, Mariano Martinez Peck <
marianopeck@gmail.com> wrote:
> > > > On Thu, Jan 26, 2012 at 11:38 AM, Igor Stasenko <
siguctua@gmail.com> wrote:
>> >> >> On 26 January 2012 11:25, stephane ducasse <
stephane.ducasse@gmail.com> wrote:
>> > >> >> phew... done reading through overquoting :) >> >> >> >> +1000 to removing tryXYZprimitive: >> >> >> >> I was always wondering what those methods for, until i met a
need to
>> >> support debugging when invoking nativeboost-prim methods, >> >> because it needs special handing when invoking methods with
native
>> >> code via debugger. >> >> >> >> Then i understood that this mechanism is necessary.. yet a
bit awkward..
>> >> >> >> Funny. Even after implementing the fix, I still do not
understand why all these is needed. Can someone explain to a newbie why invoking primitives (whether they are normal primitives, named primitives or NB primitives) from the debugger is different than invoking them normally (as when they are invoked by normal code)
>> > >> > Yes I want to understand too. >> > >> >> Here the hint for you: >> - what should happen when you doing 'step in' on method, which
has a primitive?
>> >> Apparently, it should invoke that primitive , otherwise you will
have
>> difference between running and debugging modes, >> and will have different results, which makes debugger useless. >> So debugger should detect "somehow" if primitive was failed, and
then
>> step in into given method, >> or if its not, then step in = step over. >> >> And these 'tryXYZ ... ' is exactly for solving this dilemma. > > > Thanks Igor. So I wrote what I understood. Problem is that I
always write for newbies (like me) so if it is too obvios or too long to put it as comment, let me know.
> I would appreaciate if someone can validate what I wrote. > Today I will create a slice with mentioned solution + comments +
removal of all those tryNamedPrimitive*.
> > ---- > > "When using the debugger we want to run a method step by step.
But what happens when we do a step into a CompiledMethod which has a primitive? If such a method is executed form outside the Debugger (normal scenario) the VM knows that such CompiledMethod has a primitive declaration and hence executes it. If it fails, then it continues executing all the bytecodes of the method. Otherwise, it just returns.
> > Now, what is the problem with the Debugger? The problem is that
if the primitive fail, we don't want that the VM directly executes all the remaining bytecodes of the method. Instead, we would like to go step by step with he Debugger, just as happens with normal methods.
> > To solve the mentioned problem, we use the following trick: We
have the original compiled method (the one that has a primitive invocation), the receiver and the arguments. So the idea is to use a template compiled method that ONLY contains the primitive declaration (it doesn't include all the original smalltalk code after the primitive). #tryNamedPrimitiveTemplateMethod answers such a template method which looks like:
> > tryNamedPrimitive > <primitive:'to be set later' module:'to be ser later'> > ^ ContextPart primitiveFailToken' > > Since this method does not change its bytecodes for every
invocation, we can reuse it for all methods with primitives. There are only 2 things we have to change in the template: the number of arguments and the primitive declaration (to use the correct primitive name and module name).
> > Then what we do is to run that compiled method with the receiver
and arguments we have. The result is that we will be invoking almost the same original method but a slightly different version that does not have the smalltalk part after the primitive and that in contrast is sends #primitiveFailToken (which tells the Debugger what to do after). If this method invocation does not fail, then the Debugger continues debugging the sender of the primitive method. In this case, the step in is the same as step over. If the primitive fails, then the debugger continues executing the smalltalk part after the primitive method. In this case, step in is a real step in. "
> > There's another problem with using "tryXYZ" when primitive doing something weird with contexts (like block's #value) because then debugger cannot intercept switching contexts so easily, when primitive doing manipulation with contexts, because debugger have no idea where to put his next "break point" to do step-by-step evaluation.
I thinking that there should be special primitive which:
- takes a context, a receiver, a method and arguments (or just a
context, if debugger ensures to pass it in prepared state i.e. receiver and args already on stack)
- invokes method's primitive
- answers a nil if primitive failed or a new context object, which
holds an updated context state after primitive possibly manipulated with context(s)
No, not needed. Manipulation of contexts does not require
primitives since contexts are first-class, and the effect of these primitives can be simulated in Smalltalk (just as the effect of executing bytecodes can be simulated). Look at the caller of tryPrimitive:withArgs: and tryNamedPrimitiveIn:for:withArgs:, namely ContextPart>doPrimitive:method:receiver:args:. It handles all these primitives such as perform:[*], value[:value:*], withArgs:executeMethod:.
Looks like you don't read carefully. Exactly because of looking to ContextPart>doPrimitive:method:receiver:args: i proposed another way. And besides it is much simpler. If tomorrow we introduce new primitive which manipulates context, you will need to fix this method, because it duplicating knowledge of what VM does.
I disagree. ContextPart already duplicates the VM. The direction you
propose ends up putting all context functionality in the VM, which makes the system harder to understand (the SMalltalk code is more readable), harder to experiment with (one can no longer implement experiments in the image, for example as I did in implementing the closure compiler), and harder to implement (implementing and testing tricky do-lots-of-things primitives in the VM is harder than implementing and testing their equivalents in the image). So I think this primitive is a really bad idea. Its going to be much more complex than doPrimitive:method:receiver:args:.
Hmm.. are you sure about much complex? As to me it is fairly simple:
- remember current context (debugger)
- activate a debuggable context (passed as argument)
- invoke a primitive
- activate a debugger context back and return a modified debuggable
context as result.
How can you just invoke perform: without having the perform primitive execute Smalltalk? What one wants is the primitive to get one to the next context, /not/ to do all that the primitive does. Primitive perform: does a full send and evaluation, not just the lookup and initial dispatch. Block value, evaluates the block, not just activates it. The code in doPrimitive:... just does the "get me to the next context" bit.
And of course experience shows it is much quicker and easier to
distribute Smalltalk improvements than new virtual machines, so pushing important functionality down into the VM is a retrograde step. It seems out of keeping with NativeBoost and ideas such as moving the linking machinery for named primitives and the FFI up into the image, decomposing them into a more general set of smaller simpler primitives.
This is true. But exactly because of it, we need such prim! Think, how you going to debug a dynamically generated code, which manipulates a contexts, if by invoking it, your debugger will immediately lose a control?
A simulation is not an answer here, because you actually want to run your your dynamically generated code and check if it behaves well, while if you just simulate it you will never know if its ok, because then you will be testing whether your simulation behaves correctly, instead of real thing.
I don;t think this is right. One wants to run only the initial part of the primitive, not the subsequent execution it spawns.
And of course, then every time you changing the native implementation, you will also need to update a simulation code as well -> a best way to kill a project, when two independent parts must behave identically, but since a lazy developers forgetting keep them in sync, it falling apart.
Biut that didn't happen when I introduced BlockClosure did it?
This is actually a situation which we having now, because implementation of #doPrimitive: primitiveIndex method: meth receiver: receiver args: arguments is based on assumption that VM are in sync with image, and every time you change something, you have to go there and sync stuff. Why we cannot follow DRY principle?
Because one isn't repeating oneself, given that the actual execution primitives differ from the versions that do just the first bit.
So, without having it, i had to introduce an awful hack in NativeBoost, an override in ContextPart package, because i have no choice:
The test has to go somewhere... You could perhaps make it neater if you put it in doPrimitive:.
ContextPart>>tryNamedPrimitiveIn: aCompiledMethod for: aReceiver withArgs: arguments "Hack. Attempt to execute the named primitive from the given compiled method" | selector theMethod spec |
(NativeBoost isNativeMethod: aCompiledMethod) ifTrue: [ ^ NativeBoost tryRunNativeCode: aCompiledMethod for:
aReceiver withArgs: arguments onFail: [ ^ PrimitiveFailToken ]. ]. arguments size > 8 ifTrue:[^PrimitiveFailToken]. selector := #( tryNamedPrimitive tryNamedPrimitive: tryNamedPrimitive:with: tryNamedPrimitive:with:with: tryNamedPrimitive:with:with:with: tryNamedPrimitive:with:with:with:with: tryNamedPrimitive:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with: tryNamedPrimitive:with:with:with:with:with:with:with:) at: arguments size+1. theMethod := aReceiver class lookupSelector: selector. theMethod == nil ifTrue:[^PrimitiveFailToken]. spec := theMethod literalAt: 1. spec replaceFrom: 1 to: spec size with: (aCompiledMethod literalAt:
- startingAt: 1. theMethod flushCache. selector flushCache. ^aReceiver perform: selector withArguments: arguments
NativeBoost class>>tryRunNativeCode: aCompiledMethod for: aReceiver withArgs: arguments onFail: aFailBlock "run only a native code of given method, and if it fails evaluate aFailBlock instead" | src node m failObj result | self assert: (self isNativeMethod: aCompiledMethod).
src := String streamContents: [:str | str nextPutAll: 'NBDebug_stub'. 1 to: aCompiledMethod numArgs do: [:i | i = 1 ifTrue: [ str nextPutAll: ': ' ] ifFalse: [ str nextPutAll: ' with: ' ]. str nextPutAll: 'arg'. i printOn: str. ]. str cr; nextPutAll: ' <primitive: #primitiveNativeCall module:
#NativeBoostPlugin >'; cr; nextPutAll: ' ^ ''theFailLiteral'' '. ].
node := Compiler new compile: src in: aCompiledMethod methodClass classified: nil notifying: nil ifFail: [ ^ aFailBlock value ]. m := node generate: aCompiledMethod trailer. self assert: (m literalAt: 2) = 'theFailLiteral'. failObj := Object new. m literalAt: 2 put: failObj. m flushCache. m selector flushCache. result := m valueWithReceiver: aReceiver arguments: arguments. ^ result == failObj ifTrue: [ aFailBlock value] ifFalse: [ result ]
and as you can see, an implementation of "please simply run just the primitive of method" at language side is not looking nice and easy. And of course, if by chance , a native code manipulate context, then the above hack not going to work anymore. This code actually assumes that primitive plays nicely and not doing such nasty things :)
Right, and if NB starts replacing significant parts of execution then you'll want to simulate NB code, not simply execute it when in the debugger. Primitives are just that, primitives. They can't be invoked willy-nilly. They don't obey the same constraints as Smalltalk code and they'll run away if you're not careful. doPrimitive: is careful, except for FFI calls that do callbacks (one can't step into a callback). Likewise one can't step into a NB primitive that calls-back. Tricky.
-- Best regards, Igor Stasenko.
On Sat, Jan 28, 2012 at 2:23 PM, Andreas Raab andreas.raab@gmx.de wrote:
On 1/28/2012 23:09, Igor Stasenko wrote:
Another reason why we need such prim: have you tried to debug a debugger? You can't , because there is a simulation guard:
<primitive: 19> "Simulation guard" "If successful, push result and return resuming context, else ^ PrimitiveFailToken" (primitiveIndex = 19) ifTrue: [Smalltalk tools debugContext: self label:'Code simulation error' contents: nil].
This is because you cannot instruct VM that the code you stepping over should not escape out of your control.
Sorry, but that's not what the simulation guard is for. Historically the VM would just crash when trying to simulate certain methods (like the above). So I put in simulation guards to prevent VM crashes. If the VM can simulate these methods, then you can just take them out.
Quite right. I ripped it out of the Qwaq/Teleplace codebase without ill-effect. I need to do that for Squeak. Thanks for the reminder. Simply remove the <primitive: 19> nonsense from doPrimitive:.
Cheers,
- Andreas
vm-dev@lists.squeakfoundation.org