# Tom Lord's Hackery

# 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` functions

> The 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 positive integer error codes when reporting error conditions to callers. Must functions which can indicate an error do so by returning a negative integer 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.

home

## GNU Arch

what is GNU Arch?

GNU Arch Bug Database

popular GNU Arch links

GNU Arch development roadmap

popular GNU Arch writings