Interface ConstructOutputSink

All Known Implementing Classes:
FileConstructOutputSink, InMemoryConstructOutputSink

public interface ConstructOutputSink
Where generated bytecode goes, and where it is read back from.

The destination used to be welded into every writer as new FileOutputStream(target), which forced one file per construct - 254,724 of them on a 1M-SLOC compile. That is measurably the constraint on CODE_GENERATION_AGGREGATES, the largest phase in the compiler: when it was parallelised, USER time grew 1.10x while SYS time grew 2.55x, and at 34 threads the phase was back to sequential speed. The computation parallelises; a quarter of a million creat/write/close sequences contending in the kernel do not.

The files were also an intermediate artefact rather than the deliverable: they used to be written, then read back off disk to build package.jar, which ek9 -r runs. Making the destination a policy removed that round trip - the CLI batch path now holds the bytes and the jar is built from them, an incremental build can write only what changed, and the LLVM backend can hold everything for a whole-program LTO view.

See SPEC-incremental-builds.md for the full design.

Implementations must be thread-safe. CODE_GENERATION_AGGREGATES fans out over CONSTRUCTS - one flat list across every source - and a single construct's generation can emit several classes (the construct itself plus synthesised enum iterators and aspect proxies).

  • Method Summary

    Modifier and Type
    Method
    Description
    Optional<byte[]>
    read(File target)
    Read back a class this sink was given, if it has it.
    void
    write(File target, byte[] content)
    Accept one generated class.
  • Method Details

    • write

      void write(File target, byte[] content)
      Accept one generated class.
      Parameters:
      target - the file this class WOULD occupy. It is the identity of the output, not necessarily a path that gets written: a jar sink relativises it to an entry name, an in-memory sink uses it as a key. Keeping it a File means the naming stays identical to the pre-sink pipeline, which is what makes byte-identical output verifiable.
      content - the class bytes.
    • read

      Optional<byte[]> read(File target)
      Read back a class this sink was given, if it has it.

      This exists because the sink is the single source of generated bytes, and something inside the compile needs to read them back: ByteCodeDirectiveListener validates @BYTECODE directives during compilation and used to go straight to the filesystem. That is correct only while the bytes are on the filesystem, so it would fail - loudly, but for a confusing reason - the moment a caller chose a sink that holds them. Asking the sink removes the question rather than documenting it.

      Parameters:
      target - the same file identity that was passed to write(File, byte[]).
      Returns:
      the bytes, or empty if this sink does not have them.