Class ConstructIrManifest
This is the record an incremental build reads to decide what to regenerate. Hashing the
IR rather than the source is the point: in EK9 a change in file A can alter file B's
bytecode while B's source is untouched - dispatch tables are built from concrete subtypes, the
COMPUTE_FRAMES hierarchy map depends on supertypes, and _fieldSetStatus encodes
field positions as bit indices. Where the deciding fact is in the construct's OWN IR, an IR hash
catches it automatically and needs no invalidation graph - which is where such schemes
usually fail.
WARNING - that is true of two of those three cases, not all three. Corrected
2026-09-13; see docs/implementation/EK9_INCREMENTAL_BUILD_POSITION.md section 4. Dispatch
tables (phase7.DispatchTableGenerator) and _fieldSetStatus
(phase7.IRGenerator) are computed during IR generation and ARE in the IR. The
COMPUTE_FRAMES hierarchy map is NOT: CodeGenerationAggregates.buildTypeHierarchy
builds it at CODE GENERATION time from construct.getSymbol().getSuperAggregate() - the
symbol table, not the IR. So if two types both gain an intermediate supertype, a third construct
that merges them at a join point has byte-identical IR and a different required frame, and this
manifest will say it is current.
MEASURED, no longer only reasoned (2026-09-13). The negative-case test now exists as the
sibling of spec gate 4: compiler-cli IncrementalFrameInvariantE2ETest. Insert an
intermediate supertype above two classes and leave the construct that merges them untouched; the
real ir-manifest.txt reports that construct's hash as identical
(9D8EE9D8… in both builds) while its class file goes from 1207 to 1260 bytes — a
third StackMapTable entry naming the new intermediate, plus its constant-pool entry. The
two re-parented classes DO change hash, because their supertype is in their own IR; only the
third-party construct that merges them is missed. An incremental build keyed on this manifest
would skip it and leave a stack map naming the wrong type.
The general invariant the no-graph property rests on: phases 15-22 must be a pure function of
the IRConstruct handed to them plus the fingerprinted globals.
That is currently asserted, and buildTypeHierarchy already violates it.
IRConstruct.toString() is the serialisation used, because it is already the one the
@IR directive golden files are byte-compared against across the whole test corpus. It is
therefore known-stable rather than assumed-stable.
ONE file, not one per construct - a manifest per output would add 254,724 files to remove 254,724 files.
⚠️ Deliberately NOT java.util.Properties. EK9 fully-qualified names contain
::, and : is a Properties key/value separator, so
scale.billing.domain::Customer=deadbeef loads as key scale.billing.domain with
value :Customer=deadbeef. Every key silently collapses to one entry per MODULE instead of
one per construct - a plausible-looking file with the wrong contents. Tab-separated instead; EK9
identifiers cannot contain a tab.
See SPEC-incremental-builds.md S5b.
-
Constructor Summary
Constructors -
Method Summary
-
Constructor Details
-
ConstructIrManifest
public ConstructIrManifest()
-
-
Method Details
-
isEnabled
public static boolean isEnabled()Whether this manifest is being built. When false nothing is recorded and no file is written.- Returns:
- true if manifest construction is enabled.
-
record
-
size
public int size() -
writeTo
Write the manifest.- Parameters:
target- where to write it.buildFingerPrint- identifies the configuration that produced these hashes - target architecture, optimisation level, debug flag, compiler version. A reader whose fingerprint differs MUST full-build: hashes from an optimised build say nothing about an unoptimised one, and a chance match would ship a mismatched artefact.
-