Skip to content

Commit 6c739be

Browse files
authored
[jvm] dex compatibility issues (#12898)
* [tests] add dex checker for all jvm tests * [jvm] Add jvm.dex-compatible define (noop for now) * [jvm] avoid invalid anon field names with -D jvm.dex-compatible * [tests] add test * [tests,jvm] check for dex issues * [tests] add test for closure overload issue * [jvm] merge overload closures into a single class generate_dynamic_access used to emit one switch case + one closure class per Haxe field, but @:overload @:native(X) makes several fields share a JVM name. The dispatch switch ended up with duplicate cases for the same string (the second one dead code), and each case spawned its own closure class with an identical path -- so the second class silently overwrote the first in the jar. d8/r8 reject the duplicate jar entries. Group fields by name before building cases, and add a multi-overload closure builder that emits one inner class with one invoke method per overload signature. All overload jsigs get registered in the closure cache so later user-code references resolve to the merged class. Repro at tests/misc/jvm/projects/Issue12897 (Main2/Check2). * [tests] fix runci regression * [CI] reduce impact on CI times * [tests] add server tests for shared JVM closure classes Three scenarios — overload merge via Reflect, direct member closure, single-method dynamic access via Reflect — each spans two callers and goes through clean / per-module invalidate / full invalidate cycles. Asserts the (target, method) closure is materialized exactly once as a top-level class under jvm/$Closure/ regardless of which caller's codegen triggered the registration. * [jvm] share closure classes across host modules Closure classes for member-method references used to live as inner classes of whichever caller's codegen happened to emit them first, producing a separate closure class per caller per (target, method). Move them to top-level shared classes under jvm/$Closure/ named Closure_<target_pkg_mangled>_<method>; codegen sites only register their needed invoke signatures (and any functional-interface bindings) and a finalize step materializes each closure class once with all accumulated invokes. The synthetic package segment `$Closure` is unreachable from Haxe source — Haxe identifiers reject `$` — so user code cannot define colliding classes there. Compilation-server safe: JVM codegen re-walks all reachable types on every build, so the closure cache repopulates from scratch each cycle and the finalize pass writes every needed class regardless of which modules were cache-hit at the typing layer. typed_function gains a typed_function_host variant (inner vs. standalone) selecting between caller-nested and top-level closure class spawning; local-function and static-method-reference closures keep their existing caller-nested layout. * [tests] normalize CRLF in Issue12898 stdout assertion JVM Sys.println uses the platform line separator, so the jar emits \r\n on Windows. Strip CR before comparing. * [jvm] always sanitize anon field names; drop jvm.dex-compatible define The dex-compatible anon-field handling (route fields whose names aren't valid DEX SimpleNames through DynamicObject._hx_fields instead of as typed JVM fields) is now unconditional. The user-visible difference for the cases this affects — anon structures with unsafe-named keys — is a small access-cost increase (map lookup vs. getfield); safe-named fields are unchanged. Producing jars that are dex-clean by default is the expected behavior, so the opt-in gate adds complexity without value. CI: drop the second per-target build pass. The single existing jar from each target is verified against d8 directly. * cleanup diff * Wrong 'issue' number * [jvm] drop shared-closure registration from _hx_getField The dynamic_level >= 2 branches in generate_dynamic_access registered a shared closure class for every method reachable via reflection, just so Reflect.field could hand back a typed closure. Fall back to the same readFieldClosure dispatch level 1 uses; direct obj.method closures are unaffected. * [tests] drop server tests for removed reflective-closure path testOverloadedClosureViaReflect and testSingleMethodViaReflect asserted the shared-closure invariant for Reflect.field at dynamic-level=2, which no longer holds after that path was reverted to readFieldClosure dispatch. testDirectMemberClosure still covers the unchanged obj.method path. * [jvm] cleanup dynamic-level=2 leftovers
1 parent 7f1127c commit 6c739be

13 files changed

Lines changed: 643 additions & 114 deletions

File tree

src-json/define.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@
551551
{
552552
"name": "JvmDynamicLevel",
553553
"define": "jvm.dynamic-level",
554-
"doc": "Controls the amount of dynamic support code being generated. 0 = none, 1 = field read/write optimization (default), 2 = compile-time method closures",
554+
"doc": "Controls the amount of dynamic support code being generated. 0 = none, 1 = field read/write optimization (default)",
555555
"platforms": ["jvm"]
556556
},
557557
{

0 commit comments

Comments
 (0)