Toward Condensers
Dan Heidinga
heidinga at redhat.com
Fri Aug 4 15:13:29 UTC 2023
On Wed, Aug 2, 2023 at 11:44 AM Brian Goetz <brian.goetz at oracle.com> wrote:
> I'll answer your questions with more questions.
>
> Part of your question depends on: is applying an update to a model
> (producing a new model) expensive enough that it is better to let an
> updater also act as a model-view of sorts, or should you just apply the
> update whenever you want to make changes visible to later operations, and
> move on? Secondarily, it raises the question about what the natural
> granularity of updates is: should a condenser batch all its updates into a
> single updater, or should it generate finer-grained updates?
>
> As you point out, you have choices of how to write the condenser --
> incrementally or batched. Your initial example is incremental, searching
> for classes (B) with lambdas, and rewriting both that class and the nest
> host (A and B) together. You then raise the possibility of accumulating a
> data structure of classes with lambdas to be rewritten, and doing it all at
> once. This question of how to approach classfile transformation is
> essentially the same question as how to approach updating the application
> model, just moved around a bit.
>
> My working assumption is that applying an update to a model will
> eventually be cheap enough that either option 2 or option 3 will always be
> preferable to option 1. Option 1 involves a lot of "do the same thing two
> ways", which means more code, more surface, more confusion, and more chance
> for things to get out of sync.
>
I started out thinking I wanted Option 1 but the more I played with api,
the more it became a pain to work with and it degraded the value of the
separation between Model and Updater. Option 2 & 3 leave the issue in the
users hands to determine how granular they want to make their updates,
assuming generating a Model from the Updater is cheap. And if it's not,
then batching (Option 2) is always possible.
Conclusion: separation of concerns good. Aim for ModelUpdate being
inexpensive.
>
>
> To your question about classpath keys / module keys, the data model says
> these are singletons (the double circles.) In the current (mostly
> mechanical) mapping of data model to API, for each attribute of these
> singleton keys, we generate a Stream-bearing method to iterate the
> attribute values (e.g., modules() and classPath()). The actual
> ClassPathKey, being a singleton, is completely uninteresting (its an empty
> record) so it didn't get a representation in the API. (You might
> reasonably characterize this as premature API optimization, and that's a
> fair discussion.)
>
Thanks for the clarification.
A further question about the classpath handling: in
ModelUpdater::addToClassPath are new entries appended or prepended? I can
see reasons why condenser authors might want either behaviour.
Assuming it appends, if a condenser author desperately wanted to prepend,
they could work around it via:
ContainerKey[] cp = model.classPath().toArray(ContainerKey[]::new);
ModelUpdater updater = model.updater();
for ( ContainerKey ck : cp) {
updater.removeFromClassPath(ck);
}
updater.addToClassPath(specialFirstCK);
for ( ContainerKey ck : cp) {
updater.addToClassPath(ck);
}
While we should hold off on convenience apis for now, I can see classpath
ordering being an area that needs some experimentation.
--Dan
>
> On 8/2/2023 10:36 AM, Dan Heidinga wrote:
>
> Is there a way to query the current value from a ModelUpdater?
>
> The use case I'm thinking of is likely to be fairly common where updating
> one class requires updates to another (ie: Nest attributes). Take the
> follow nest of classes and a condenser that pregenerates lambdas as an
> example:
>
> class A {
> NestMembers: [B, C]
> }
>
> class B {
> NestHost: A
>
> void foo() { Runnable r = () -> ....; }
> }
>
> class C {
> NestHost: A
>
> void bar() { Runnable r = () -> ....; }
> }
>
> When processing class B, a new class will be generated and it will be
> necessary to update A's NestMembers to include the pregenerated class so
> the constraint that the Host knows its members is maintained. Similarly,
> when processing class C, A's NestMembers will again need to be updated.
>
> I think this becomes the following calls to the APIs:
>
> public ApplicationModel condense(ApplicationModel model) {
> // NIT: Should this be ApplicationModel or Model? Examples use
> ApplicationModel but interface is named "Model"
>
> ModelUpdater updater = model.updater();
>
> // ----- process class B ------
> // update the nestHost's nestMembers
> ClassKey classK = getNestHostClassKey(?????);
> updater.addToContainer(classK,
> updateNestHost(model.classContents(classK))); // First fetch of
> classContents
>
> // ----- process class C ------
> // update the nestHost's nestMembers
> ClassKey classK = getNestHostClassKey(?????);
> updater.addToContainer(classK,
> updateNestHost(model.classContents(classK))); // Critical line:
> where do I get classContents?
>
> return model.apply(updater);
> }
>
> The critical question is where do I get the classContents for the second
> update to the classK? The Model is immutable so the second query will get
> the original bytes and lose the changes done by the first update.
>
> Option 1: Query the ModelUpdater instead? That would imply that
> ModelUpdater extends Model which may be ugly to patch the Stream's to
> return the updated values instead.
>
> Option 2: Batch the updates and only apply them once? That's what I do
> now in the jlink plugin but it means carrying more state in the condenser.
> It keeps this as the users problem to manage state
>
> Option 3: Generate a new Model after every transformation?
>
> Model m = model;
> {
> ModelUpdater mUpdater = m.updater();
> // Process class B
> m = m.apply(mUpdater);
> }
> {
> ModelUpdater mUpdater = m.updater();
> // Process class C
> m = m.apply(mUpdater);
> }
>
> All three options are workable but result in different API shapes. Option
> 3 has some nice debugging properties in that it's always easy to see what
> changed in each transformation and it may play well with structured logging
> as discussed in the other thread.
>
> An unrelated question: where does ClassPathKey() or ModulesKey() show up
> in the model? They aren't subtypes of ContainerKey which is where I
> expected them to appear. How do you see them being used?
>
> Sorry for the deluge of questions / comments. I'm excited to see the
> progress here.
>
> --Dan
>
>
> On Mon, Jul 31, 2023 at 4:31 PM Mark Reinhold <mark.reinhold at oracle.com>
> wrote:
>
>> A few of us have been thinking about how condensers might work. We now
>> have a prototype design and implementation of a condenser API and tool.
>>
>> We’ve deliberately started small, focusing on principles of condenser
>> operation and a minimal set of features sufficient for the simplest
>> condensers, i.e., those that don’t require additional changes to the
>> Platform Specification.
>>
>> Design note:
>> https://openjdk.org/projects/leyden/notes/03-toward-condensers
>>
>> Summary:
>>
>> We elaborate the concept of composable condensers to introduce a
>> simple, abstract, immutable, data-driven model of applications so that
>> condensers can be expressed as transformers of instances of the model.
>> The model is sufficient to express simple condensers; we include two
>> examples.
>>
>> This is just a starting point; we expect to evolve it considerably going
>> forward. We’ll publish the prototype code shortly after we return from
>> the upcoming JVM Language Summit.
>>
>> - Mark
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/leyden-dev/attachments/20230804/762e4a01/attachment.htm>
More information about the leyden-dev
mailing list