Commit 6c739be
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 leftovers1 parent 7f1127c commit 6c739be
13 files changed
Lines changed: 643 additions & 114 deletions
File tree
- src-json
- src/generators
- jvm
- tests
- misc/jvm
- dex
- projects/Issue12898
- runci/targets
- server/src/cases/issues
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
551 | 551 | | |
552 | 552 | | |
553 | 553 | | |
554 | | - | |
| 554 | + | |
555 | 555 | | |
556 | 556 | | |
557 | 557 | | |
| |||
0 commit comments