So to motivate the "compress the shuple" approach - one of my eventual goals (not necessarily with the near-term implementation) is to try to remove ObjectGroups entirely. That was the original design intent with the Shypes proposal, and I don't wan't the shuples implementation approach to block that eventual goal.
As per Chris's analysis, it's turning out that the objects to ObjectGroups ratio is very low (5 to 1 or so), and it's mostly due to ObjectGroups for functions.
As far as I can tell, there are two reasons ObjectGroups are needed for functions - one is to identify the canonical function for the group. These show up because of scoped functions that are allocating multiple function instances per canonical function (one per activation).
If there are no unusual changes to the function (i.e. added properties, etc.), then the default ObjectGroup for a function should very only by the underlying canonical function. The property type-sets for the default properties can be derived automatically otherwise. This means that only functions which have unusual things done to them will need ObjectGroups. The standard JS usage of inner functions (callbacks, handlers, higher-order code) should all be able to infer everything about the object from the fact that it's 1) a function, and 2) the canonical function for it.
In the short term, it probably doesn't make sense to implement all of this. But it's the long-term motivation behind the idea of doing a shuples implementation where we "compress" the shuple representation and inline it directly into the (now single-word) type pointer for an object.
For now, that can be just a pointer to the existing group infrastructure with a boolean flag indicating that it's a standard function shaped thing. In the future, it should be possible to eliminate the ObjectGroup allocation for Function entirely, and just have a tagged pointer to the canonical function stand in as an object-group for the function.
If Chris's measurements are right, that'll save us at least the size of an ObjectGroup (minimally 48 bytes) across ~12k objects, which is easily over 500k memory per compartment, which is a nice win to have in our pocket (and something of value to present to towards larger org goals - e.g. Fission. It would eliminate shuples memory overhead for a large class of objects, and allow for a lot more freedom (in terms of memory overhead) to play with shuples representation.
The tagged pointers-to-canonical functions should work for singleton functions as well - as we can check to see if the canonical function is the same and assume a singleton type if so.