GROOVY-12138: emit invokedynamic call sites for property writes (spike)#2674
GROOVY-12138: emit invokedynamic call sites for property writes (spike)#2674paulk-asert wants to merge 2 commits into
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2674 +/- ##
==================================================
- Coverage 68.7019% 68.6439% -0.0580%
- Complexity 34123 34126 +3
==================================================
Files 1536 1536
Lines 128912 129018 +106
Branches 23369 23400 +31
==================================================
- Hits 88565 88563 -2
- Misses 32517 32619 +102
- Partials 7830 7836 +6
🚀 New features to boost your workflow:
|
Property writes were the last un-cached dispatch surface: even in indy mode they compiled to static ScriptBytecodeAdapter.setProperty calls, paying getMetaClass() plus a full MetaClassImpl.setProperty resolution walk on every write, in both indy and classic modes. Behind the compile-time flag groovy.indy.setproperty (default: off), AsmClassGenerator now diverts plain (non-safe, non-spread) property writes to a new CallSiteWriter.makeSetPropertySite hook. The default implementation is the classic adapter call, so classic mode and flag-off builds are unchanged; IndyCallSiteWriter overrides it to emit invokedynamic setProperty:(ReceiverType, ValueType)V with the receiver first, preserving the runtime's arguments[0]-is-receiver invariant. The new SetPropertySelector (replacing the 'not supported' GroovyBugError for CallType.SET) fast-paths only shapes whose resolution is provably sender- and metaclass-independent: a public setter whose backing field is private or absent (GROOVY-8283 field-precedence is sender-dependent), or a public non-final field, each accepting the runtime value type directly. Everything else — Maps (GROOVY-8065/11367), Class receivers, categories, overridden/mixed-in/expando setProperty (detected by both a reflection probe and a metaclass pickMethod probe; ExpandoMetaClass is excluded wholesale, mirroring the GET selector), value coercion, listeners, propertyMissing — binds the sender-aware ScriptBytecodeAdapter.setProperty as the call-site handle, making non-fast-path behavior identical to the classic path by construction. Guards, PIC, and hit-count promotion come free from the existing call-site machinery; the value participates in the argument-class guards so type changes at a site re-select correctly. Measured: ~42x on a hot property-write loop (6M mixed setter/field writes: ~435ms -> ~10ms, written state consumed and verified). Full root test suite (15678 tests) passes with the flag enabled for all test-code compilation; a 12-scenario semantic stress produces identical output compiled with and without the flag.
| // otherwise (no meta property, listeners, expando, propertyMissing, ...): adapter path | ||
| } | ||
|
|
||
| private static final Class<?>[] SET_PROPERTY_PARAMS = {String.class, Object.class}; |
There was a problem hiding this comment.
just for style, this should be before the methods. Nothing blocking in my eyes
| MethodHandle sba = MethodHandles.insertArguments(SBA_SET_PROPERTY, 1, sender); | ||
| sba = MethodHandles.insertArguments(sba, 2, name); // -> (Object value, Object receiver)void | ||
| handle = MethodHandles.permuteArguments(sba, | ||
| MethodType.methodType(void.class, Object.class, Object.class), 1, 0); // -> (receiver, value)void |
There was a problem hiding this comment.
I would consider not using SBA directly here. Instead I would make a method maybe even in the the selector, that has the arguments already in the right order and allows you to use a single insertArguments to inject sender and name and has the right remaining signature for the invocation. For example (name, receiverClass, receiver, value). Then one insertArguments call would be enough. You can still let that one call SBA... though I imagine we want to inline the remaining logic there in the long run.
There was a problem hiding this comment.
changed if I understood you correctly
✅ All tests passed ✅🏷️ Commit: 9ce62e0 Learn more about TestLens at testlens.app. |
Property writes were the last un-cached dispatch surface: even in indy mode they compiled to static ScriptBytecodeAdapter.setProperty calls, paying getMetaClass() plus a full MetaClassImpl.setProperty resolution walk on every write, in both indy and classic modes.
Behind the compile-time flag groovy.indy.setproperty (default: off), AsmClassGenerator now diverts plain (non-safe, non-spread) property writes to a new CallSiteWriter.makeSetPropertySite hook. The default implementation is the classic adapter call, so classic mode and flag-off builds are unchanged; IndyCallSiteWriter overrides it to emit invokedynamic setProperty:(ReceiverType, ValueType)V with the receiver first, preserving the runtime's arguments[0]-is-receiver invariant.
The new SetPropertySelector (replacing the 'not supported' GroovyBugError for CallType.SET) fast-paths only shapes whose resolution is provably sender- and metaclass-independent: a public setter whose backing field is private or absent (GROOVY-8283 field-precedence is sender-dependent), or a public non-final field, each accepting the runtime value type directly. Everything else — Maps (GROOVY-8065/11367), Class receivers, categories, overridden/mixed-in/expando setProperty (detected by both a reflection probe and a metaclass pickMethod probe; ExpandoMetaClass is excluded wholesale, mirroring the GET selector), value coercion, listeners, propertyMissing — binds the sender-aware ScriptBytecodeAdapter.setProperty as the call-site handle, making non-fast-path behavior identical to the classic path by construction. Guards, PIC, and hit-count promotion come free from the existing call-site machinery; the value participates in the argument-class guards so type changes at a site re-select correctly.
Measured: ~42x on a hot property-write loop (6M mixed setter/field writes: ~435ms -> ~10ms, written state consumed and verified). Full root test suite (15678 tests) passes with the flag enabled for all test-code compilation; a 12-scenario semantic stress produces identical output compiled with and without the flag.