Local calls, remote calls, closure calls, tuple calls, p-mod calls. The code server. Linking. Hot code loading, purging. (Overlap with Chapter 4, have to see what goes where.) Higher order functions, Implementation of higher order functions. Higher order functions and hot code loading. Higher order functions in a distributed system.
In Erlang there is a semantic difference between a local function call and a remote function call. A remote call, that is a call to a function in a named module, is guaranteed to go to the latest loaded version of that module. A local call, a unqualified call to a function within the same module, is guaranteed to go to the same version of the code as the caller.
A call to a local function can be turned into a remote call by specifying the module name at the call site. This is usually done with the ?MODULE macro as in ?MODULE:foo(). A remote call to a non local module can not be turned into a local call, i.e. there is no way to guarantee the version of the callee in the caller.
This is an important feature of Erlang which makes hot code loading or hot upgrades possible. Just make sure you have a remote call somewhere in your server loop and you can then load new code into the system while it is running; when execution reaches the remote call it will switch to executing the new code.
A common way of writing server loops is to have a local call for the main loop and a code upgrade handler which does a remote call and possibly a state upgrade:
loop(State) ->
receive
upgrade ->
NewState = ?MODULE:code_upgrade(State),
?MODULE:loop(NewState);
Msg ->
NewState = handle_msg(Msg, State),
loop(NewState)
end.
With this construct, which is basically what gen_server uses, the programmer has control over when and how a code upgrade is done.
The hot code upgrade is one of the most important features of Erlang which makes it possible to write servers that operates 24/7 year out and year in. It is also one of the main reasons why Erlang is dynamically typed. It is very hard in a statically typed language to give type for the code_upgrade function. (It is also hard to give the type of the loop function) These types will change in the future as the type of State changes to handle new features.
For a language implementer concerned with performance, the hot code loading functionality is a burden though. Since each call to or from a remote module can change to new code in the future it is very hard to do whole program optimization across module boundaries. (Hard but not impossible, there are solutions but so far I have not seen one fully implemented.)
In the Erlang Runtime System the code loading is handled by the code server. The code server will call the lover level bifs in the erlang module for the actual loading. But the code server also determines the purging policy.
The runtime system can keep two versions of each module, a current version and an old version. All fully qualified (remote) calls goes to the current version. Local calls in the old version and return addresses on the stack can still go to the old version.
If a third version of a module is loaded and there still are processes running (have pointers on the stack to) old code the code server will kill does processes and purge the old code. Then the current version will become old and the third version will be loaded as the current version.
The BEAM loader does not just take the external beam format and writes it to memory. It also does a number of transformations on the code and translates from the external (generic) format to the internal (specific) format.
The code for the loader can be found in beam_load.c (in erts/emulator/) but most of the logic for the translations are in the file ops.tab (in the same directory).
The first step of the loader is to parse beam file, basically the same work as we did in Erlang in [CH-beam_modules] but written in C.
Then the rules in ops.tab are applied to instructions in the code chunk to translate the generic instruction to one or more specific instructions.
The translation table works through pattern matching. Each line in the file defines a pattern of one or more generic instructions with arguments and optionally an arrow followed by one or more instructions to translate to.
The transformations in ops tab tries to handle patterns of instructions generated by the compiler and peephole optimize them to fewer specific instructions. The ops tab transformations tries to generate jump tables for patterns of selects.
The file ops.tab is not parsed at runtime, instead a pattern matching program is generated from ops.tab and stored in an array in a generated C file. The perl script beam_makeops (in erts/emulator/utils) generates a target specific set of opcodes and translation programs in the files beam_opcodes.h and beam_opcodes.c (these files end up in the given target directory e.g. erts/emulator/x86_64-unknown-linux-gnu/opt/smp/).
The same program (beam_makeops) also generates the Erlang code for the compiler back end beam_opcodes.erl.