Prim error returns (was Re: [squeak-dev] The Primitive: I am not a number- I am a named prim! - SqueakPeople article)

Eliot Miranda eliot.miranda at gmail.com
Tue Jul 1 22:07:14 UTC 2008


On Tue, Jul 1, 2008 at 2:23 PM, tim Rowledge <tim at rowledge.org> wrote:

>
>
>>
>> I did primitive error codes at Cadence and they'll very probably b making
>> it into Cog real soon now.  They're simpler than VisualWorks', being only
>> symbols.  So extracting more information, such as a related error code
>> requires a subsequent call.  But I think the work I'm doing right now on
>> eliminating pushRemappableOop:/popRemappableOop will enable me to have a
>> structured object with a name and parameters, which is more generally
>> useful.
>>
>
> So.... what is the anticipated way that will work? I'm not sure I see the
> need for a two step process anyway.
>
> The version we did at Interval in 98 was very simple, providing a call for
> prims to stuff some object (we used SmallInts in prcatice, but anything was
> allowed) into the first slot in the context that got activated on the fail.
> We extended the primitive declaration pragma to allow optional naming of the
> tempvar - default was 'errorValue' I think - and that was it. Code following
> the prim call could use or ignore the temp.


The compiler is modified to recognise <primitive: integer errorCode:
identifier> and <primitive: identifier module: identifier errorCode:
identifier> and convert this into one additional temp and generate a long
storeTemp as the first instruction of the method.

The VM is modified to, on primitive failure, check for the  long storeTemp
and stores the error code if it sees it.

Old images lack the storeTemp so the VM does not store the value.  Old VMs
do perform the storeTemp, but because the storeTemp is storing into the
stack top it is effectively a noop.  e.g. if the primitive looks like:

*primitiveAdoptInstance:* *anObject***
    <*primitive:*** 160 error: *ec***>
    *ec*** == *#'no modification'*** ifTrue:
        [*^****NoModificationError***
            signal: *self***
            message: (*Message***
                        selector: *#primitiveAdoptInstance:***
                        arguments: {*anObject***})].
    *self*** primitiveFailed

then the header says the method has 2 temps (anObject & ec).  The VM
initializes the context with stackp pointing at the second temp (ec).  The
merthod starts with a long storeTemp: 2.  So the nil ec gets stored into
itself.  The compiler generates a long storeTemp to make it quicker to check
for the method starting with a storeTemp.


>
>>
>> A nice thing is that the code is forwards and backewards compatible.  One
>> can use the VM to run older images.  One can run images that contain the
>> primitive error code on older VMs, where one simply gets a nil error code on
>> primitive failure.
>>
>>  Sure, if the temp is used by return-code-unaware methods in an older
> image then it can't make much difference because it would be assumed nil as
> normal. Running new images on older VMs isn't something we've ever much
> bothered with for Squeak so don't worry about it.
>
> I'm going to guess that "work I'm doing right now on eliminating
> pushRemappableOop:/popRemappableOop" relates to getting allocations out of
> primitives as much as possible, right?  That would be nice.


No.  This relates to not having to use pushRemappableOop:/popRemappableOop
because it is extremely error-prone.  For example can you spot the bug in
this:

*primitivePerform*
    | *performSelector*** *newReceiver*** *selectorIndex*** *lookupClass***
*performMethod*** |
    *performSelector*** *:=* *messageSelector***.
    *performMethod*** *:=* *newMethod***.
    *messageSelector*** *:=* *self*** stackValue: *argumentCount*** - 1.
    *newReceiver*** *:=* *self*** stackValue: *argumentCount***.

    *"NOTE: the following lookup may fail and be converted to
#doesNotUnderstand:, so we must adjust argumentCount and slide args now, so
that would work."***

    *"Slide arguments down over selector"***
    *argumentCount*** *:=* *argumentCount*** - 1.
    *selectorIndex*** *:=* *self*** stackPointerIndex - *argumentCount***.
    *self***
        transfer: *argumentCount***
        fromIndex: *selectorIndex*** + 1
        ofObject: *activeContext***
        toIndex: *selectorIndex***
        ofObject: *activeContext***.
    *self*** pop: 1.
    *lookupClass*** *:=* *self*** fetchClassOf: *newReceiver***.
    *self*** findNewMethodInClass: *lookupClass***.

    *"Only test CompiledMethods for argument count - other objects will have
to take their chances"***
    (*self*** isCompiledMethod: *newMethod***)
        ifTrue: [*self*** success: (*self*** argumentCountOf: *newMethod***)
 = *argumentCount***].

    *self*** successful
        ifTrue: [*self*** executeNewMethodFromCache.
            *"Recursive xeq affects successFlag"***
            *self*** initPrimCall]
        ifFalse: [*"Slide the args back up (sigh) and re-insert the
            selector. "***
            1 to: *argumentCount*** do: [:*i*** | *self***
                        storePointer: *argumentCount*** - *i*** + 1 + *
selectorIndex***
                        ofObject: *activeContext***
                        withValue: (*self*** fetchPointer: *argumentCount***
 - *i*** + *selectorIndex*** ofObject: *activeContext***)].
            *self*** unPop: 1.
            *self*** storePointer: *selectorIndex***
                ofObject: *activeContext***
                withValue: *messageSelector***.
            *argumentCount*** *:=* *argumentCount*** + 1.
            *newMethod*** *:=* *performMethod***.
            *messageSelector*** *:=* *performSelector***]


Don't look further.  try and spot the bug first.







It starts with
    *performSelector*** *:=* *messageSelector***.
    *performMethod*** *:=* *newMethod***.
and ends with
            *newMethod*** *:=* *performMethod***.
            *messageSelector*** *:=* *performSelector
which is only executed if there is a doesNotUnderstand:.*
A doesNotUnderstand: may cause a GC in createActualMessage as it creates the
message argument for doesNotUnderstand:.  So this bug only bytes if a
perform is not understood when memory is on the verge of exhaustion.

In the context-to-stack-mapping VM I'm working on the VM may do up to a page
worth of context allocations on e.g. trying to store the sender of a
context.  Rather than try and plug all the holes it is much safer to
restrict garbage collection to happening between bytecodes (probably on
sends and backward branches).  To do this the GC has to maintain a reserve
for the VM which is about the size of two stack pages, or probably 2k bytes,
and the VM has to defer incremental collections from allocations until the
send or backward branch following.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.squeakfoundation.org/pipermail/squeak-dev/attachments/20080701/f559d668/attachment.htm


More information about the Squeak-dev mailing list