Anatomy of a Librified libarch
Function
#include <tla/libarch/arch.h>
It may be helpful to read this document in parallel with the template C code for
libarch
functionsThe template is described in detail in the appendix Template Details.
t_arch const arch
-- The Universal Function Parameter
Every ordinary function in libarch
accepts a parameter of type
t_arch
, conventionally named arch
. Some very low level
functions are an exception to this rule.
By convention, the output parameters to a function are first
in its parameter list, then the t_arch const arch
parameter,
and then any input parameters.
Arch Sessions and Arch Calls
The arch
parameter holds the state, including dynamically
allocated resources, of a single "arch session" and of all "arch
calls" within that session.
An arch call is the invocation and execution of a single,
client-facing, ordinary libarch
function. Typically an arch
call performs a primitive arch operation or calculation. A call may
temporarily allocate resources as it runs; these resources are
released before the call returns to the client. During a single
call, temporary resources are "attached" to the t_arch const arch
and, when a call returns, are automatically released.
An arch session is a sequence of calls which may share
persistent state among themselves. A t_arch const arch
value
is the name for a single context in which such persistent state
can be recorded. For example, the location of a user's
.arch-params
directory defaults to ~/
but it might as easily be
an element of the persistent state of a t_arch const arch
object.
A client can allocate a t_arch
, set its .arch-params
location
to a non-default location, and then perform a series of arch
calls with that t_arch
. Those arch calls will use the
non-default .arch-params
location. Other calls in the
same process, using a different t_arch
, might use a
different .arch-params
location.
Anatomy of an Arch Function
Entry and Exit
libarch
extends the standard C function prolog and
postlog with its own conventions. Every libarch
function
must be of the general form (here illustrated for a function
which can not raise errors):
int some_fn (t_arch const arch) { int answer = -1; arch_enter_call (arch); /* ... compute the real answer. Set `answer'. ... */ arch_leave_call (arch); return answer; }
Unbalanced calls to arch_leave_call
constitute an invariant
violation resulting from a coding error and cause a
call to panic
.
Error Codes and Raising Errors
libarch
uses dynamically assigned positiveinteger error codes
when reporting error conditions to callers. Must functions which
can indicate an error do so by returning a negativeinteger
whose magnitude (additive inverse) is an error code.
In addition to signalling errors via return value, functions must
explicitly raise errors, which records then in the t_arch const
arch
parameter. Typical code looks (schematically) like this:
ssize_t some_fn (t_arch const arch) { ssize_t answer = -1; ssize_t errcode = 0; arch_enter_call (arch); /* ... compute the real value for `answer' but: */ if (... an XYZZY error has been detected ...) { errcode = error_arch_XYZZY (); goto error_exit; } /* ... assuming that `answer' is now set */ invariant (answer >= 0); /* normal return can't be negative -- * that's reserved for errors. */ arch_leave_call (arch); return answer; error_exit: invariant (errcode > 0); arch_RAISE (arch, errcode, "some_fn"); answer = -errcode; arch_leave_call (arch); return answer; }
See also Error State Transition Table.
Error Messages -- The msgbuf
Between a call to arch_enter_call
and a call to arch_RAISE
,
clients can format arbitrary text to be associated with the
raised error. (The intent is that this is text which
might be displayed to a user.)
The exmaple above might be modified to contain:
if (... an XYZZY error has been detected ...) { arch_msgbuf_printfmt (arch, "an xyzzy error caused by %s\n", xyzzy_errstr()); errcode = error_arch_XYZZY (); goto error_exit; }
Managing Allocted Local State
Commonly, libarch
functions need to allocate temporary strings,
lists, and tables to contain the results of intermediate
computations. Those data structures must be reliably freed
before returning to the caller. The situation is common enough
that libarch
includes help for it.
Local allocated values, when allocated, are "attached" to a t_arch
const arch
value. When the call is exited (indicated by a call to
arch_leave_call
), those attached resources are freed:
WARNING: this interface is not fully implemented yet and is subject to minor changes as it is finalized.
ssize_t some_fn (t_arch const arch) { ARCH_FN_DECL; ssize_t answer = -1; t_local_val tmp_str = LOCAL_VAL_NIL_INIT; ssize_t status = -1; arch_enter_call (arch); /* ... */ /* e.g.: allocate a local string */ status = val_make_str (addr_local_val (tmp_str, arch), arch, "hello world"); if (status < 0) { errcode = arch_error_ENOMEM (); goto error_exit; } /* ... */ arch_leave_call (arch); /* * The call to `arch_leave_call' above will implicitly * release the storage allocated for `tmp_str'. */ return answer; error_exit: invariant (errcode > 0); arch_RAISE (arch, errcode, "some_fn"); answer = -errcode; arch_leave_call (arch); return answer; }
Appendix: {Template Details}
The file src/tla/Template-fn contains
a template for librified libarch
functions. The template begins:
Function Prototype
/** function: FN_NAME * */ ssize_t FN_NAME (t_arch const arch)
there and elsewhere, replace FN_NAME
with the name of the function
you are defining.
Most functions should return a ssize_t
value, a negative integer
indicating an error (whose error code is the magnitude of that
negative integer). Functions with no other use for the ssize_t
return value should return 0
upon success. Other functions may
return any non-negative value upon success.
Output parameters should be declared prior to the arch
parameter;
other parameters following the arch
parameter. All parameters
should be declared const. Normally, each parameter should be
declared on a separate line. For example:
ssize_t some_fn (int * const n_ret, t_arch const arch, int const x, int const y)
Local Variable
All non-const local variables must be declared in the outermost block of a function. The outermost block in the template begins:
{ ARCH_FN_DECLS; ssize_t answer = -1; int pc = 0; ssize_t errcode = 0;
Additional local variables may be added to that list.
The sole exception to this role is local variables which are
const
. Such variables may be declared (and necessarily
initialized) at the top of nested blocks.
No local variable in the outermost block may have a non-constant
initializer or may have an initializer which makes a call to
a function (even a const
function).
Function Entry
The body of the template reads, schematically:
if (pc == 0) { /* init */ arch_enter_call (arch); goto body; } else { [... error handling code ...] normal_exit: invariant (answer >= 0); goto exit_with_cleanups; exit_with_cleanups: goto exit_immediately; exit_immediately: arch_leave_call (arch); return answer; } body: [... main body of the function ...] goto normal_exit;
Programs may add code to the section labeled /* init */
between the call to arch_enter_call
and the
goto body
statement. Coded added in that location
should be solely for the purpose of initializing local
variables to values which are not constant. For example,
a local variable declared:
time_t now = 0;
might be initialized in the /* init */
section with:
time_t now = time (0);
It could not have been declared these ways:
time_t now = time (0); /* illegal (by convention) */ time_t const now = time (0); /* illegal (by convention) */
because local variable initializers at the top of a libarch
function may not make function calls.
Single Entry, Single Exit
The template contains the code:
exit_immediately: arch_leave_call (arch); return answer;
That is the only return
statement in the template and programmers
must not add addition return
statements to the function
definition. All exit paths from the function must pass through the
code labeled exit_immediately
.
The call to arch_leave_call
provides run-time enforcement of an
open ended set of calling convention invarients. It has has
responsibility for releasing temporary resources allocated by this
function call.
Error and Normal Returns but a Single Cleanup Path
The template contains:
error_exit: invariant (errcode > 0); arch_RAISE (arch, errcode, "FN_NAME"); answer = -errcode; goto exit_with_cleanups; efatal_exit: errcode = error_arch_EFATAL (); goto error_exit; eparam_exit: errcode = error_arch_EPARAM (); goto error_exit; normal_exit: invariant (answer >= 0); goto exit_with_cleanups; exit_with_cleanups: goto exit_immediately;
Programmers may delete the code labeled efatal_exit
and
eparam_exit
if those exit paths are not needed.
All error exit paths from the function should involve setting
errcode
to a positive integer value and transfering to
error_exit
.
All ordinary exit paths from the function should involve setting the
variable answer
to the return value from the function and
transfering to normal_exit
.
When those conventions are followed, execution passes through the
code labled exit_with_cleanups
and finally to`exit_immediately'.
Programmers may (have to) customize the template in two ways:
(1) By adding resource-releasing code to the section labled
exit_with_cleanups
; (2) by modifying the invariant
's in
error_exit
and normal_exit
to more precisely reflect the
range of expected return values from the function.
The Main Function Body
The template finishes with:
body: goto normal_exit;
Programmers must customize that part of the template by filling in
code implementing the function between the body
and the goto
.
In some cases, programmer's must replace the label in the goto
or
replace the entire goto
with /* not reached */
.
Appendix: {Error State Transition Table}
Any libarch
function (client facing or ordinary internal function)
can raise an error. Caller's must respond to all errors raised.
To an imperfect but practical extent, libarch
enforces that
restriction. In particular, every t_libarch
is partly
characterized by an error state. The error state is
described by three integers, according to the state diagram below.
The gist is that if a function you call raises an error, your client must either re-raise the error before returning to its caller, or must "clear" the error state.
NORMAL state: depth = D err_depth = -1; last_raise_depth = -1; invariants: 0 <= D transitions: * arch_enter_call (arch) goto NORMAL with new_depth := D + 1 * arch_leave_call (arch) if (depth == 0) FATAL ERROR else goto NORMAL with new_depth := D - 1 * arch_RAISE (arch, errcode, tag) goto ERROR_HERE with new_err_depth := D new_last_raise_depth := D * arch_clear_error (arch) goto NORMAL with err_depth = -1; last_raise_depth = -1; ---- ERROR_HERE state: depth = D err_depth = E; last_raise_depth = L; invariants: D >= 0 D <= E D == L transitions: * arch_enter_call (arch) FATAL ERROR * arch_leave_call (arch) if (depth == 0) FATAL ERROR else goto ERROR_CALLEE with new_depth := D - 1 * arch_RAISE (arch, errcode, tag) goto ERROR_HERE * arch_clear_error (arch) goto NORMAL with err_depth = -1; last_raise_depth = -1; ---- ERROR_CALLEE state: depth = D err_depth = E; last_raise_depth = L; invariants: D >= 0 D < E D == (L - 1) transitions: * arch_enter_call (arch) FATAL ERROR * arch_leave_call (arch) FATAL ERROR * arch_RAISE (arch, errcode, tag) goto ERROR_HERE with new_last_raise_depth = D * arch_clear_error (arch) goto NORMAL with err_depth = -1; last_raise_depth = -1;
Copyright
Copyright (C) 2004 Tom Lord
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
See the file COPYING
for further information about
the copyright and warranty status of this work.