You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Link: with alternatives as plain edges, fernflower's StructClass.create exceeds the work ceiling through a callee #89
With alternatives as plain edges ("alternatives are not aliases", #72), fernflower's StructClass.create(DataInputFullStream, boolean, LazyLoader) exceeds the work ceiling, is degraded to a shallow summary, and so becomes nonModifying=false. Without the change it is analysed in full and non-modifying.
create marks no alternatives in its own statements: the retry without them (LinkComputerImpl.doMethod, which saves DomHelper.findGeneralStatement) does not trigger. So the extra cost comes in through a callee whose summary changed under the change, and makes create's own link graph heavier.
What is known
A/B on laser1, PARALLEL=1, NOALTSOURCES NOLIBRARYRETURN against neither: the only fernflower FPDUMP difference, and the only degraded method that differs (17 without, 18 with).
Every attempt is a work ceiling ([work ceiling] in the "Cycle protection tripped" line), in every iteration.
A class isolate of StructClass, and of DomHelper together with its reached types, doesn't reproduce such blowups: the callees there are stubs, or too close (maddi#180 for the isolate's own defects).
Next step
Find the callee: log LINKWORK for create's callees on both sides (a run with the link computer's debug logging limited to the org.jetbrains.java.decompiler.struct package) and compare their summaries. Then decide between a rule that keeps that shape grouped and a retry that also covers a method whose callees used alternatives.
Found and fixed on ws/maddi-null-89 (9d4c768), on top of the #72 stack rebased onto devel (ws/maddi-null-72stack). Not merged yet; guava is being measured.
The callee
StructClass.create marks no alternatives itself, and without them it already uses 53–60% of the work ceiling. One callee's summary changed: GenericMain.parseClassSignature, through GenericType.parse. Logging every recipient of alternatives (a probe, not committed) showed two kinds marked there, neither of them aliasing the method can observe:
a value without identity: type = getType(c) (one of CodeConstants' ints), wildcard = switch (..) { .. };
a call result whose sources are all objects the callee created: descriptor.superclass = GenericType.parse(superCl) takes one of four new GenericType(..) (oc:122, oc:126, oc:129, oc:133). As plain edges the result's fields went from ≺ oc (field of) to ≈ oc (shares fields with), both ways, and create's closure grew from 9.1k to 12.9k edges.
The fix
Both keep their group (Graph.aliasingIsMoot, gates NOALTPRIMITIVE, NOALTFRESH). guava's TypeResolver case has the caller's own locals as sources and is unaffected (TestCallResults.alternativeSources).
fernflower, laser1, PARALLEL=1
alternatives off
rules off
both rules
primitive only
fresh only
StructClass.create
53% of ceiling
trips
53%
trips
53%
trips
262
280
275
280
275
retries without alternatives
0
273
117
273
137
run time
29 min
43 min
35 min
43 min
37 min
With both rules, fernflower's verdicts equal those with alternatives off.
Still open
The retry is costly. About a dozen methods trip with alternatives, are retried without them, and trip again, in every iteration (117 retries). Remembering that a method degrades either way would save most of the remaining 6 minutes.
SWITCHFORK=1 on fernflower moves 2 summary lines and no trips.
TypeResolver.resolveParameterizedType: resolvedOwner is nullable with the fix (A: not null), resolvedRawType stays not null. The case the alternatives were introduced for holds under the fix.
ImmutableMap.copyOf(Iterable): the elements of entryArray move from nullable (A) to not null (B).
From the earlier laser1 run, at modification level (FPDUMP): the fix changes nothing compared with the change before it (R vs B, 0 lines). Alternatives themselves change 252 lines against A, e.g. ImmutableSet.SetBuilderImpl.build() and three ContiguousSet.*Impl methods become non-modifying. SWITCHFORK=1 changes 44 more lines (B vs S).
The 19 trips are the same in both legs, so they are not caused by alternatives.
Activity
bnaudts commentedon Nov 3, 2026
Found and fixed on
ws/maddi-null-89(9d4c768), on top of the #72 stack rebased onto devel (ws/maddi-null-72stack). Not merged yet; guava is being measured.The callee
StructClass.createmarks no alternatives itself, and without them it already uses 53–60% of the work ceiling. One callee's summary changed:GenericMain.parseClassSignature, throughGenericType.parse. Logging every recipient of alternatives (a probe, not committed) showed two kinds marked there, neither of them aliasing the method can observe:type = getType(c)(one of CodeConstants' ints),wildcard = switch (..) { .. };descriptor.superclass = GenericType.parse(superCl)takes one of fournew GenericType(..)(oc:122, oc:126, oc:129, oc:133). As plain edges the result's fields went from≺ oc(field of) to≈ oc(shares fields with), both ways, andcreate's closure grew from 9.1k to 12.9k edges.The fix
Both keep their group (
Graph.aliasingIsMoot, gatesNOALTPRIMITIVE,NOALTFRESH). guava's TypeResolver case has the caller's own locals as sources and is unaffected (TestCallResults.alternativeSources).StructClass.createWith both rules, fernflower's verdicts equal those with alternatives off.
Still open
bnaudts commentedon Nov 3, 2026
Guava check, KOTLIN-pass nullability on guava with every verdict and local dumped (PARALLEL=1, Mac):
Of 69,598 verdict and local lines, 2 differ:
TypeResolver.resolveParameterizedType:resolvedOwneris nullable with the fix (A: not null),resolvedRawTypestays not null. The case the alternatives were introduced for holds under the fix.ImmutableMap.copyOf(Iterable): the elements ofentryArraymove from nullable (A) to not null (B).From the earlier laser1 run, at modification level (FPDUMP): the fix changes nothing compared with the change before it (R vs B, 0 lines). Alternatives themselves change 252 lines against A, e.g.
ImmutableSet.SetBuilderImpl.build()and threeContiguousSet.*Implmethods become non-modifying. SWITCHFORK=1 changes 44 more lines (B vs S).The 19 trips are the same in both legs, so they are not caused by alternatives.
bnaudts commentedon Nov 5, 2026
Merged into devel (fix 9d4c768, gates NOALTPRIMITIVE and NOALTFRESH). The remaining work-ceiling retries are #92.