Rho.Dyn.Reuse (rho v0.1.0)

Copy Markdown View Source

Incremental compiles: a proposal recompiles only the files that changed (or depend on a change at compile time) and reuses the rest from its parent generation, renamed. On a Moto G 2021 compiling the default front from source takes about 33 s, and recompiling a module from its stored Erlang code still about 350 ms (the Erlang compiler's passes): 24 s of a one-file change to the 68-module front. So a module is reused by renaming its compiled binary instead; on a Mac that takes 56 ms for the whole front, against 1.9 s through the Erlang compiler.

What a generation records (deps, stored next to its binaries, Rho.Dyn.Store.deps/2), by the names the agent wrote ("Dyn.Notes"):

  • per file, the modules it defines and the modules it needs at compile time (Rho.Dyn.Trace), or nil when that's unknown;
  • per module, the modules its compiled code names anywhere (calls, literals, attributes), from its debug info; a reused module names what its parent named.

Which files are reused (plan/2): an unchanged file whose needs are known, none of which is dirty. A module is dirty if its file isn't reused (changed, deleted, or itself dirty), or if its code names a dirty module, directly or through others: a compile-time call into it may reach the change. Repeated until nothing changes.

Reusing (recompile/4): the parent's binary with every Dyn.G<parent>.* atom in its atom table, literals and term chunks (debug info, attributes, docs, ...) renamed to Dyn.G<n>.*. Code refers to atoms and literals by index, so it is kept byte for byte, line table included: stack traces name the parent generation's copy of the source, which is the same text. A module whose code or literals still hold the parent generation's name (a string built at compile time, say), or that the rename doesn't understand, goes the slow way: the Erlang code in its debug info, renamed, through the Erlang compiler. If that still holds the old name or doesn't compile, it's compiled from source. Generations built by another runtime (a Core update) are never reused from: the Keeper rebuilds them whole.

Summary

Functions

Empty deps: nothing known, nothing reused.

The files of deps (the parent's) to reuse, given the files whose source is the same as the parent's (unchanged).

Generation to's version of bin, a module of generation from, for source file file: {:ok, module, binary}, or :error when it has to be compiled from source.

The modules of generation n (by their written names) that bin's code names, other than itself; nil when it has no debug info (it might name any).

Types

deps()

@type deps() :: %{required(String.t()) => map()}

Functions

empty()

@spec empty() :: deps()

Empty deps: nothing known, nothing reused.

plan(arg1, unchanged)

@spec plan(deps(), MapSet.t(String.t())) :: MapSet.t(String.t())

The files of deps (the parent's) to reuse, given the files whose source is the same as the parent's (unchanged).

recompile(bin, from, to, file)

@spec recompile(binary(), pos_integer(), pos_integer(), String.t()) ::
  {:ok, module(), binary()} | :error

Generation to's version of bin, a module of generation from, for source file file: {:ok, module, binary}, or :error when it has to be compiled from source.

refs(mod, bin, n)

@spec refs(module(), binary(), pos_integer()) :: [String.t()] | nil

The modules of generation n (by their written names) that bin's code names, other than itself; nil when it has no debug info (it might name any).