Class ConstructIrManifest

java.lang.Object
org.ek9lang.compiler.backend.ConstructIrManifest

public final class ConstructIrManifest extends Object
ONE file mapping every generated construct to a hash of the IR it was generated from.

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 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

      public void record(String fullyQualifiedName, Supplier<String> irText)
      Record the IR hash for one construct.
      Parameters:
      fullyQualifiedName - the construct's EK9 FQN - the key an incremental build looks up.
      irText - supplies the construct's IR serialisation, called only when enabled.
    • size

      public int size()
    • writeTo

      public void writeTo(File target, String buildFingerPrint)
      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.