 Fiasco style guide

 Michael Hohmuth

; To generate a printable version of this document, please type 
; cd ../doc/style; make

Introduction
############

Motivation and intended audience
================================

This document is meant as a quick reference to a number of guidelines
meant for existing and especially new Fiasco kernel developers.  


How to follow these rules: Metarules
====================================

This document does not differentiate between strict rules and ``soft''
guidelines.  Also, when browsing the source code, you will notice a
large number of deviations from these rules (because most rules are
younger than the current kernel code).  In this sense, all rules are
``soft.''

However, as the master guideline, when writing new code, please ensure
that the number of rule violations does not grow.  (In the future, we
might even enforce this property automatically when someone is
checking in code.)


Programming language, dialect, and subset
#########################################

Fiasco has been written in C++.  However, it uses C++ in a special
dialect supported by the Preprocess tool [Hohmuth: Preprocess].  In
effect, programmers do not need to write C++ header files, and do not
need to declare (member) functions -- Preprocess automates these
tasks.

We use some features of Preprocess for configuration
management---please refer to Section [Configuration-specific source code].

Preprocess lacks support for the following C++ features, which
therefore cannot be used in Fiasco source code (but can be used in
third-party source code used, i.e., can be included, by Fiasco):

* Name spaces --- use static class interfaces or global or static free
  functions instead (Section [Singletons])
* Nested classes --- use forward-declared ``private classes'' instead
* '#ifdef' and 
  '#if' on file top level, except for 
  '#if 0' --- use
  Preprocess' configuration features instead (Section
  [Configuration-specific source code])

Some features of C++ are explicitly disallowed because Fiasco contains
no run-time support for them:

* Exceptions
* Run-time type identification and dynamic_cast

These features are always disabled on the compiler command line.

On the other hand, templates are allowed.  However, please keep in
mind that Fiasco's source code needs to be interpreted not only by the
latest version of GCC, but also by at least the two preceding stable
versions of GCC, _and_ ---more significantly---by the VFiasco project's
semantics compiler, which is a custom compiler _we_ (in a broader
sense of we) have to maintain.  Therefore, using advanced tricks such
as expression templates or partial specialization is strongly
discouraged.


Source-code organization and directory structure
################################################

Subsystems
==========

Fiasco consists of a number of subsystems; among them:

:KERNEL: The kernel proper.  This is the part that implements the L4
  specification.

:ABI: ABI-specific definitions, mostly type definitions and accessor
  functions.

:JDB: The built-in kernel debugger.

:BOOT: The bootstrapper.  This part sets up virtual memory, copies the
  kernel to its standard virtual-address-space location (0xf0001000),
  and runs the kernel.

Subsystems are defined in the Modules file, which is the main
configuration file for Fiasco's build system.  Please refer to
the build-system documentation [README] for more information on this
build system.

Directory structure
===================

Subsystems usually reside in their own directory under src (the
exception being those subsystems which do not contain any source code,
but which exist only for maintenance purposes).

Inside each (source-code) subsystem directory such as kern, the
directory layout is as follows:

:kern/: Source files shared for all Fiasco-supported
  architectures

:kern/shared/: Source files shared by more than one, but not
  all architectures.

:kern/ia32, kern/ia64, kern/ux, and so on: Source files that are
  specific for only one architecture.

Currently, hardware architectures is the only configuration dimension
that motivates moving a source file into a subdirectory.  In other
words, source files pertaining to a particular configuration option
(other than hardware architecture), but not another, are located in
one of the mentioned directories.

Source-file naming
==================

Usually, source files belong to exactly one module, consisting of one
main C++ class plus, optionally, public utility functions, small
interface classes for exchanging data with the main class, and private
auxiliary classes and functions.  A module can be comprised of
multiple source files.  These source files all start with the name of
the module's main class, in all lower case (e.g., for class
Thread_state, file names start with the string "thread_state").  When
multiple source files implement one module, each file name (except for
the main file's) add a submodule-specific suffix, separated with a
dash (-).  For example:

* kern/thread.cpp
* kern/thread-ipc.cpp

Fiasco's build system mandates that all source files (of all
subsystems) have different names, even if they reside in different
directories.  To make file names different, our naming convention is
to add architecture-configuration strings to the file names, separated
by dashes (-).  For example:

* kern/thread.cpp
* kern/shared/thread-ia32-ux.cpp
* kern/ia64/thread-ia64.cpp

Occasionally, it is useful to separate configuration-specific code
into a source file of its own (see Section 
[Configuration-specific source code]).  In this case, the file name
contains the configuration string as a suffix, separated by dashes
(-).  For example:

* kern/thread-v4.cpp
* kern/thread-v2x0.cpp
* kern/ia32/thread-ia32-smas.cpp


Header files and the C++ preprocessor
=====================================

As Preprocess assumes the task of writing header files, programmers
should not add new header files.  The exception to this rule is that
header files are required when defining constants that are needed by
both assembly code and C++ code.

When using header files, these files must be protected from multiple
inclusions using include-file guards, as in the following example for
the file config_gdt.h:

!  #ifndef CONFIG_GDT_H
!  #define CONFIG_GDT_H
!  // File contents
!  #endif 


Configuration management
########################

The configuration tool
======================

The interactive configuration tool is started using "make menuconfig".
The tool creates two files, intended for inclusion in C++ code and in
makefiles:

:globalconfig.h: This file defines a preprocessor symbol for each
  _enabled_ configuration option.

:globalconfig.out: This file defines a Make variable for set _each_
  configuration option.  Variables for enables options are set to "y",
  those for disabled options are set to "n".

Adding new configuration options
--------------------------------

Help texts for configuration options in the rules file (rules.cml) are
sorted by order in which the options appear in the menu defined at the
bottom of the file. New configuration options must add such a help text
describing what the config option does. After a new config option
has been added, define under which conditions the config option should
be suppressed, if any. It is generally a good idea to suppress config
options for architectures and configurations where the option is
meaningless. Options which can be suppressed for certain configurations
usually require consistency rules to ensure they are not left in an
undefined state from a previous selection.

An example suppression rule is:         
!  when UX suppress SERIAL
The corresponding consistency rule is:
!  require UX implies (SERIAL == n)

This ensures that 'SERIAL' is set to "n" when someone had previously chosen
'IA32' and 'SERIAL' set to "y" and then changes the architecture to 'UX'.

Do not forget to update the configuration templates in the directory
src/templates after modifying configuration options.

Class Config
============

This class defines boot-time and constant configuration variables for
Fiasco.  

The constant variables can be derived from options the configuration
tool has written to globalconfig.h.  As a special exception, '#ifdef'
is allowed here.

Boot-time configuration variables can be derived from the kernel's
command line (class Cmdline).

Configuration-specific source code
==================================

Single or multiple source files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Source code that is specific to one or a single combination of
configuration options can reside in a separate file or in the same
file as code for other configuration options.  The decision of which
works better is in the developer's discretion.  Developers should try
to combine logically cohesive code fragments in one source file, even
if the fragments are mutually exclusive.

Conditional compilation
~~~~~~~~~~~~~~~~~~~~~~~

Using preprocessor constants and '#ifdef' for conditional compilation
is only allowed for source code passed to the assembler (assembly
files and header files meant to be included in assembly code).  In C++
source code, this style is discouraged, and Preprocess does not even
support it (for top-level conditional compilation).

Configuration-specific C++ code blocks are labeled using Preprocess'
configuration-tag feature with 'IMPLEMENTATION' and 'INTERFACE'
directives.  

This feature is available only on file top level.  Conditional
compilation inside function or class blocks is not allowed.  Instead,
programmers have the following options:

:For class definitions:
  Use Preprocess' 'EXTENSION' feature to extend class data with
  configuration-specific contents.

:For function definitions:
  # Create a Boolean constant in class Config that depends on the
    configuration option ('#ifdef' is allowed there; see Section
    [Class Config]), and use the normal C++ 'if' statement to
    conditionalize the code.  We rely on the C++ compiler's optimizer
    to remove dead code.
  # Factor out configuration-specific functions with a common
    interface.


Compile-time polymorphism
=========================

Many configurable aspects of Fiasco are implemented as a set of
(member) functions that implement a given interface in a specific way.
However, unlike typical C++ programs, Fiasco usually does not define
these interfaces as abstract base classes and then derives
configuration-specific subclass implementations from them.  We have
deemed the overhead of virtual-function calls as too high in many
cases.  

Instead, in Fiasco these interfaces are not declared as virtual
functions, but as nonvirtual functions.  These functions are then
defined in configuration-specific 'IMPLEMENTATION' sections, which can
be combined in one file or spread across multiple files (see Section
[Configuration-specific source code]).  Preprocess assists in this
kind of compile-time polymorphism by allowing configuration-specific
inline functions and private member functions.

These interfaces do not need to be split out into separate classes.
It is often useful to have a part of a class interface that is
implemented in a configuration-specific way.

Usually, Preprocess assumes the task of copying (member-) function
declarations into a module's public header file, and this is our
preferred usage.  However, for interfaces that are implemented in a
configuration-specific source-code block, we make an exception: The
``public'' interface (i.e., the interface used by generic client code,
which not necessarily needs consist of only public member functions)
can be declared and documented in the 'INTERFACE' section of the
corresponding module.  To prevent Preprocess from adding another
declaration, the definition needs to add the 'IMPLEMENT' directive.
The prerequisite is that the interface is implemented in an
'IMPLEMENTATION' section that depends on _more_ configuration options
than the 'INTERFACE' section containing the declaration.  For example:

!  INTERFACE:
!  
!  class Pic
!  {
!  public:
!    /**
!     * Static initalization of the interrupt controller.
!     */
!    static void init();
!    // ...
!  };
!  
!  IMPLEMENTATION[i8259]:
!  
!  IMPLEMENT 
!  void 
!  Pic::init()
!  {
!    pic_init(0x20,0x28);
!  }

Maintainer-mode configuration
=============================

Fiasco's build process can be instrumented with a number of checks
that ensure some of the rules defined in this document.  Fiasco
developers should enable this option in the interactive configuration
tool; the option is called 'MAINTAINER_MODE' (``Do additional checks
at build time'').

The checks enabled by this option include:
* Checking for mutual or circular dependencies between modules (see
  Section [Dependency management])
* Checking for use of deallocated initialization data after
  initialization time.


Dependency management
#####################

Fiasco has been designed to be configurable and robust.  A
precondition for achieving these properties is that modules and
subsystems can be removed from the kernel and tested in isolation,
which in turn depends on the absence of mutual or circular
dependencies between modules and subsystems.

Therefore, as a rule, these dependencies must be avoided.  Please
consult [Lakos: Large-scale C++ Software Design] for standard methods to
resolve circular dependencies.

The current dependency graph can be generated in text or graphics form
using "make DEPS" and "make DEPS.ps".


Source-code style and indentation
#################################

Naming conventions
==================

In general, Fiasco developers despise the ugly
MixedCapsNamingConvention made popular by Java.  If you really must
use such names, please go hack some Java project, not Fiasco.  In
Fiasco, words in multi-word identifier names are generally separated
with underscores, with the sequencing words starting with a lowercase
letter or a digit (the only exception being preprocessor symbols).
Examples:

!  Funky_type
!  thread_id
!  Thread_ready
!  _current_sched
!  _thread_0

In particular, the conventions are as follows:

* Type names (class names, typedef names, enum names) all start with a
  capital letter.  Examples: 'Thread', 'Jdb', 'Boot_info'

* Function names (both member functions and free functions) start with
  a lowercase letter.  Examples: 'fpage_map()', 'ipc_send_regs()'

* Nonstatic member variables start with an underscore (_).  Examples:
  '_mode', '_ready_time'

* Other variables (including static member variables, global and local
  variables, function-argument names) start with a lower-case letter.
  Examples: 'preempter', 'cpu_lock'

* Enumeration constants start with a capital letter. Examples:
  'Thread_ready', 'Page_writable'

  The Fiasco architecture board has declared that having the same
  naming convention for types and constants is not confusing.

* Preprocessor-symbol identifiers are all-uppercase and start with a
  letter.  As with all other identifiers, multiple words are separated
  using underscores.  Examples: 'THREAD_BLOCK_SIZE', 'CONFIG_IA32_486'

  Please note that preprocessor constants are deprecated and allowed
  only in assembly files and header files meant to be included in
  assembly code (see Section [Constant definitions]).

(For file-naming conventions refer to Section [Source-file naming].)

Comments
========

All comments must be in English.  American / Aussie / Kiwi English are
OK, too, but Pidgin English or Denglisch are not.

Doxygen comments
~~~~~~~~~~~~~~~~

Please document at least all interfaces of all classes that are meant
for client-code use.  These interfaces usually include all public and
protected member functions and all nonstatic free functions, but
possibly more if there is a private interface implemented by
configuration-specific code.

The interface documentation belongs to the function definition, except
if configuration-specific functions (with the 'IMPLEMENT' directive)
are declared in an 'INTERFACE' section.  In the that case, the
documentation belongs to the declaration.

Please use Doxygen's Javadoc-like style (the style using '@' instead
of backslashes) to document your interfaces.  Fiasco's documentation
is generated using Doxygen's auto-brief feature, so '@brief'
directives are unnecessary [Heesch: Doxygen Manual].

Comment style
~~~~~~~~~~~~~

The style of multi-line comments is not prescribed, but please be
consistent within one source file.  All of the following forms are
OK:

!  /** The foo function.
!   *  This function frobnifies its arguments.
!   */
!
!  /** 
!   *  The foo function.
!   *  This function frobnifies its arguments.
!   */
!
!  /** The foo function.
!      This function frobnifies its arguments.
!  */

Marking incomplete code
~~~~~~~~~~~~~~~~~~~~~~~

Please use the token 'XXX' inside a comment to mark broken or
incomplete code.

Module-level rules
==================

Singletons
~~~~~~~~~~

Singleton objects (classes that will be instantiated only once) should
be implemented as static class interfaces instead of a normal
instantiable class in order to save kernel-stack space (the this
pointer does not need to be passed to these classes).  However,
developers should use normal classes when it is foreseeable that the
class needs to be instantiated multiple times in the future, for
instance to support SMP machines.

Constant definitions
~~~~~~~~~~~~~~~~~~~~

Constants should generally only be defined in enumerations.
Preprocessor constants are discouraged except for source code passed
to the assembler (assembly files and header files meant to be included
in assembly code).

In C++ code, preprocessor constants must not be used for conditional
compilation using '#ifdef' and friends (see Section 
[Conditional compilation]).

Bit-field types
~~~~~~~~~~~~~~~

When implementing a binary interface that has been specified in terms
of bits of machine words (such as the L4 ABI or a device interface),
it is a bad idea to implement the interface using bit-field types,
unless the interface is architecture-specific.  The reason is that the
assignment of bit-field members to bit offsets is both
compiler-dependent and architecture-dependent.  If bit fiddling is
required, please define a class that wraps an integral type (such as
'unsigned') and manipulate the bits using bit-and and bit-or
operators.

Bit-field structures are OK when the exact order of bits in the type's
memory representation does not matter.

Block-level rules
=================

Assertions
~~~~~~~~~~

Use assertions generously.

Fiasco supports two kinds of runtime assertions: 'assert' and 'check'.
Both cause a kernel panic when their argument evaluates to false.

The first, 'assert', works just like 'assert' in standard C.  It can
be removed from the build by defining the preprocessor symbol 'NDEBUG'
and therefore must not include code that has side effects.

When side effects are desired, use 'check' instead of assert.  The
contents of this macro are not optimized away with 'NDEBUG' -- only
the error-checking code is.

Common idioms
~~~~~~~~~~~~~

* Endless loops are programmed like this:

  !  for (;;)
  !    // do stuff

* Sometimes a private inline function is desired, for example if it
  needs to be wrapped by a stub that is callable from assembly code.
  In this case, use 'inline NOEXPORT' to avoid having to specify a
  lengthy 'NEEDS[]' directive for the inline function:

  !  extern "C" 
  !  void
  !  asm_callable_stub (Thread* t)
  !  {
  !    t->do_stuff();
  !  }
  !
  !  PRIVATE inline NOEXPORT
  !  Thread::do_stuff ()
  !  {
  !    // ...
  !  }

* Macros are discouraged.  Use inline functions instead.

* If you really, absolutely have to define a macro, please make sure
  it can be used as single-statement blocks after 'if', 'else', and
  the like, by wrapping it like this:

  !  #define foo(x)  do { /* your stuff */ } while (0)

Rules for spacing, bracing, and indentation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If not specified more precisely in this standard, the GNU Coding
Conventions for spacing and bracing apply ([GNU Coding Standards],
Section 5.1).

* The tabulator size is 8.  A tabulator can always be replaced with
  spaces that lead to the next tab stop.

* The maximum line length is 80.  If a code line becomes longer than
  80 characters, it must be broken into shorter lines.  If line length
  becomes excessive, developers should factor out utility functions.

* The indentation increment is 2 spaces.

* In function definitions, put both the return type and the function
  signature on a line by themselves.  Example:

  !  PUBLIC inline
  !  void
  !  Preemption::set_preempter (Receiver *preempter)
  !  {
  !    // ...
  !  }

* Put whitespace before each opening parenthesis, except if it is
  preceded by another opening parenthesis.  Put whitespace after each
  closing parenthesis, except if it is followed by another closing
  parenthesis or a comma or semicolon.  As a special exception, you do
  not need to put a space between a function name and the
  function-call operator.

  !  a = f (b(), sin ((x + y) * z));

* Do not wrap the argument to the 'return' statement into parentheses.

* Opening and closing braces ('\{', '\}', including the form '\};')
  always reside on a line of their own.  The braces are indented for
  nested code blocks, but not for type and function definitions.  The
  braced content is always indented with respect to the braces.

  Goto labels are back-indented 1 space, class-access specifiers
  (public, protected, private) are back-indented 2 spaces.

  !  class Thread
  !  {
  !  public:
  !    enum Thread_state
  !    {
  !      Thread_invalid, Thread_running
  !    };
  !
  !    int _prio;
  !  };

  !  PUBLIC
  !  void
  !  Thread::stuff ()
  !  {
  !    if (receiver()->ipc_try_lock (this) == 0)
  !      {
  !        do_stuff();
  !      }
  !  }

  As an exception, if a function definition fits into a single line,
  the whole function can be defined in one line.  This style can aid
  readability when defining many small functions in a row.

* Curly braces around single-statement code blocks for 'if', 'while',
  'do', and 'for' are optional, except if the control expression(s) of
  these statement spans more than one line, the statement following it
  must be braced.

* Spacing inside expressions is not prescribed.  Please do something
  sensible to save us from adding more rules to this document.

; Fiasco style rules
; 
; Y Directory organization
; 
; Y Naming: Classes, methods, constants
; 
; Y No exceptions, no RTTI
; 
; Y Singleton objects as static class interfaces, to save stack space
; 
; Y no preprocessor variables -- use enums
; 
; Y IMPLEMENT nur für definitionen in spezielleren Subsections
; 
; Y config class vs configuration management
; 
; Y header files: unusally not.  otherwise, multi-inc protection
; 
; Y assert, check
; 
; Y Max. 80 Zeichen pro Zeile
; 
; Y Doxygen-Style
; 
; Y Doxygen: Bei deklarierten Memberfunktionen (mit IMPLEMENT
;   implementiert) kommt die Doku vor die Deklaration
; 
; Y Member-Vars: _member_name
;   Klassen:     Class_name
;   Konstanten:  Constant_name
; 
; Y XXX-Kommentare
; 
; Y wann eigenes Quellfile, wann mehrere IMPLEMENTATION-Sections in
;   einem File?
; 
; Y Funktionname an Zeilenbeginn, Name/Parameter auf einer Zeile
; 
; Y Rückgabetyp auf Extra-Zeile
; 
; Y Einrücke-Standards
;   . Enums
; 
; Y Endlosschleife: for (;;)
; 
; Y Makros: use do {} while (0);  (else-kompatibel)
; 
; Y Use inline NOEXPORT für lokale Inlines  (ohne NEEDS)
; 
; - kleine Interfaces erwünscht -- minimiere PUBLIC
; 
; Y Makros vermeiden
; 
; Y Konstanten als enums
; 
; Y cyclic dependencies, 
;
; - initcalls
;
; - boolean return values -- bool vs Mword, maybe a new type Bool?
;
; - "Thread*" t vs "Thread *t"
;
; - Style of CVS commit messages
;
; - Code duplication avoidance
;
; - C++ default parameters -- discouraged?
;
; - Passing structures as const structure&
; 
; - Makeconf.local: the place to set CC, CXX
;
; - Sort IMPLEMENTATION[xyz] sections alphabetically: ARM vor IA32 vor
;   UX, V2 vor V4 vor X0 bzw. V2,X0 vor V4.
;
; - declaration/IMPLEMENT are not for documentation purposes, which
;   would not work anyway; use the files in auto/ or Doxygen.
; 
; ----------------------------------------------------------------------
; 
; René:
; 
; Was mir am Anfang nicht klar war:
; 
; Y Namenskonvention, member mit _foo, Klassen mit Grossbuchstaben
;   beginnen & co, Namen allg.
; 
; Y Comments englisch only
; 
; Y Klammersetzung!! Auch wo man Klammern weglassen soll!
; 
; - IMPLEMENT vs PUBLIC, PRIVATE, PROTECTED
; 
; Erwaehnenswert waere noch fuer Neuanfaenger
; 
; Y keine ifdefs
; 
; - vieleicht Waechter-Style:
;   also nicht
; 
; 	if foo
; 		if bar
; 			if foobar
; 
;   sondern eher
; 	if !foo
; 		return
; 	if !bar
; 		return 
;   usw.
; 
; Y Vieleicht sollten wir die maximale Einruecktiefe begrenzen. Ich bin fuer
;   3-4. Ich weis das vieles im Code noch nicht so ist, aber man kann ja mal
;   anfangen.
; 
; - Generic vs Arch.-spezifische Sachen, zB im generic kein regs()->ecx &
;   co.
; 
; 
; 
;   Assemblermacros:
;   Bis vor kurzen habe ich noch die #define Version genommen.
;   Macht sich aber richtig ******* beim schreiben.
;   Habe jetzt mein Zeug auf .macro umgestellt, sieht erheblich freundlicher
;   aus.
; 
;   Ein paar hinweise allg zu Assembler, wann man lieber
;   Funktionen/gemeinsamen Code sollte und wann Makros.

;Local Variables:
;mode:flyspell
;ispell-local-dictionary: "american"
;comment-start: ";"
;comment-start-skip: "; *"
;End:

; LocalWords:  accessor Hohmuth tex pdflatex README Preprocess GCC polymorphism
; LocalWords:  nonvirtual instantiable NDEBUG Doxygen's Doxygen XXX Javadoc cpp
; LocalWords:  NOEXPORT Metarules ifdef
