Rho.Dyn.Trace (rho v0.1.0)

Copy Markdown View Source

An Elixir compiler tracer for Dyn compiles (Code.put_compiler_option(:tracers, ...)): which modules of the generation a file needs at compile time, so Rho.Dyn.Compiler knows when a file compiled for an earlier generation can be reused as it is (Rho.Dyn.Reuse).

A file needs module M at compile time when it expands %M{}, imports or requires M, uses one of its macros, or calls M outside a function body (in a module attribute, say): its compiled code then depends on M's code, not only on its name. Calls inside function bodies only name M and don't count.

It records only while collect/2 runs in the calling process (the tracer runs in the process that expands the code), so other compiles in the VM are untouched. collect/2 also reports the modules the file defined (:on_module); a caller that sees fewer than it compiled treats the file's needs as unknown.

It is also the gate on what a Dyn compile may define, before anything loads (guard/3, in the compiling process): when a module's definition starts (:defmodule) it is reported to the compiler, which undoes what the compile defined outside the generation, and refused (the compile of that file fails) unless it is <ns>.G<n>.* (the namespace, Rho.Dyn.Config) or named as a protocol's implementation for one (Inspect.<ns>.G<n>.X, with Inspect a protocol). A Protocol.derive/2 for a stdlib type or a library macro's module elsewhere never replaces anything.

Summary

Functions

May a compile for generation own ("Elixir.Dyn.G<n>.") define mod: one of its own, or a protocol's implementation for one?

Runs fun (one file's compile) and returns its result with what it traced: the modules defined and the modules named prefix* it needed at compile time.

From now on in this process, every module definition that starts is sent to pid as {ref, :defmodule, module, prior} (prior: the md5 and file of the version loaded under that name until then, or nil), and one outside generation n (see the moduledoc) raises instead of being defined.

Adds this tracer to the compiler's (once per VM).

Functions

allowed?(mod, own)

@spec allowed?(module(), String.t()) :: boolean()

May a compile for generation own ("Elixir.Dyn.G<n>.") define mod: one of its own, or a protocol's implementation for one?

collect(prefix, fun)

@spec collect(String.t(), (-> result)) ::
  {result, %{defined: MapSet.t(module()), needs: MapSet.t(module())}}
when result: term()

Runs fun (one file's compile) and returns its result with what it traced: the modules defined and the modules named prefix* it needed at compile time.

guard(n, pid, ref)

@spec guard(pos_integer(), pid(), reference()) :: :ok

From now on in this process, every module definition that starts is sent to pid as {ref, :defmodule, module, prior} (prior: the md5 and file of the version loaded under that name until then, or nil), and one outside generation n (see the moduledoc) raises instead of being defined.

install()

@spec install() :: :ok

Adds this tracer to the compiler's (once per VM).