We do a combination of a priori and a posteriori coalescence with integers.  
IntegerDescriptor has a static array of instances for values in the range 
[0..255].  We can choose to use values from this array when we know we're 
working with an unsigned byte, or if we're working with an int and expect the 
cost of checking if it's actually an unsigned byte to be low enough and true 
often enough.  A similar array is used in CharacterDescriptor to make Latin-1 
characters have low cost.

However, we intentionally allow "slip" in the design.  It's perfectly legal to 
have two integer objects in memory that happen to be equal, even if they fall 
in the range [0..255] (or whatever we adjust it to).  Comparing them is 
exceptionally fast if they're the same instance of AvailObject, but if they're 
not and we happen to compare two equal AvailObjects, we replace one with an 
indirection so the next compare will be fast.  That's a best-of-both-worlds 
approach, where we don't *require* canonization, but it's allowed (and 
essentially undetectable to the Avail programmer).  Todd described a similar 
"slip" that we allow with mutable updates.  A quasi-destructive operation (like 
o_PlusCanDestroy()) on a mutable object may, but is not required to, clobber 
the object in place.  It always returns the result, which may or may not be the 
object it was given.  In fact, for o_PlusCanDestroy() it's allowed to recycle 
either the receiver or the argument, to avoid having the programmer use 
superstitious coding tricks like using "1 + n" instead of the more usual "n + 
1".

The same happens for the empty tuple, empty set, and empty map, as well as some 
commonly used types.  Note that these statically-accessible values can be 
accessed by code running in any fiber, so they're always marked shared, as are 
all function implementations (a.k.a., raw code, see 
CompiledCodeDescriptor.java), but not necessarily function closures 
(FunctionDescriptor.java).  Function closures that are used as method 
implementations or semantic restriction bodies are always marked shared.

As for garbage collection... having a garbage collector coalesce equal objects 
is another opportunistic middle ground between a priori and a posteriori 
canonization.  It allows short lived objects to avoid the canonization step 
(since they won't survive a scavenge), while allowing longer lived objects to 
take less space and be compared more quickly – since they're more likely to be 
compared a lot than objects that disappear almost immediately.

And that's just one special case of garbage-collection-induced representation 
change.  I had a simple working scavenger back in the day that was fully 
incremental.  It switched the "sense" of all the descriptors at flip time, 
turning all objects into ToSpace stubs that pointed back into FromSpace.  All 
operations on a ToSpace stub caused the fields to be scanned, creating (or 
locating) ToSpace stubs for every immediately referenced object, then switching 
the descriptor of the parent object's descriptor from ToSpace stub to the 
appropriate descriptor.  We have similar plans for a multi-threaded incremental 
(or scavenged) garbage collector that almost never has to coordinate (pause) 
all threads.  We can segregate shared objects into a common space, allowing 
fibers to use private pools until (and unless) objects become shared.

When a system is 21 years old (!) and has no installed user base to prevent 
drastic improvements, well,... those drastic improvements all happen.  :-)


On May 7, 2014, at 2:27 PM, Robbert van Dalen wrote:

> Todd,
> 
>>> lastly, are weak references first-class in avail?
>> 
>> Mark and I have reasoned that weakness implies identity, and is therefore 
>> inappropriate for most values in Avail. Weakness is certainly useful for 
>> identities though, but is currently not supported. Mark and I have talked 
>> about supporting weakness at some point in the future, because weak sets and 
>> maps are obviously very powerful. Weak sets and maps would themselves have 
>> to be identities, rather than values. (Ordinary sets and maps are just 
>> identityless values in Avail.)
> 
> indeed, weak versions of set and map have to have identity and doesn’t sit 
> well with the language.
> 
>>> i need this to be able to hash-cons objects (via weak maps)
>> 
>> Avail supports coalescence of equal objects through indirections. Check out 
>> IndirectionDescriptor, and the becomeIndirectionTo() method of A_BasicObject 
>> / AvailObject. The basic idea is that the equals() operation is usually 
>> permitted to replace one of two semantically equal participants with an 
>> indirection to the other; the method isBetterRepresentationThan() helps 
>> equals() decide which of two semantically equal objects is preferable (and 
>> which should become the indirection). Eventually Avail will run natively and 
>> provide its own garbage collector, at which time we will be able to rewrite 
>> indirections into strong pointers.
> 
> from the java documentation, i understand that a (avail native) garbage 
> collector can decide to coalescence equal objects, while it traverses the 
> live set.
> That’s a very cool idea.
> 
> but is it possible to somehow implement hash-consing (a-priori coalescence) 
> in avail?
> i want hash-consing to be able to do garbage collectable memoization.
> via indirections, it appears you can only achieve a-posteriori coalescence 
> via equals?
> 
> anyway, i think it is great that avail has all these notions baked in, and 
> not as a afterthought.
> indeed, avail is truly about advanced value and identity programming!
> 
> i don’t understand why avail isn’t getting much love on the internet.
> for me it’s the best next thing since sliced bread.
> 
> cheers,
> Robbert.

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

Reply via email to