Cosm Programmers Guide
Version: p2-2008-09-06

Overview

The purpose of this document is to establish common programming methods for those developing code compatable with the Cosm libraries.

Development Cycle

Since we use SVN the development cycle is continuous. Coders should make sure they are familiar with SVN.

Coordination

_Any_ questions at all, ask. Before implementing any additional low level functions inform me, so we can make sure there isn't any overlap. I intend to be familiar with every line of code, so if you need any "major" functionality let me know, someone else may already be working on it. Also, if you're going to do anything "neat" be sure and inform me first, just so more than one person knows what you're doing.

Use of Other Code and Libraries

All code must be original and your own writing, you can't cut-n-paste from _any_ other projects or code libraries due to license problems (and other code won't follow the Cosm standards anyway). We've made sure there are no license problems with the couple of libraries that are eventually needed, and even then they will be completely separated from the main code.

Simplicity

Simplicity and clarity in the code are more important than weird optimization tricks. Especially since all the real work is done in the cores. All code interfaces are designed to be as simple as possible.

Thread Safe Code

All Cosm routines must be fully thread safe. This is not optional. If you don't know how to use locks correctly please go read up, there are many sources on the net on using locks. Keep in mind also that 64 and 128bit data types may not be atomic.

Portability

Straight ANSI C + GNU Assembly. Period.
For numerous reasons of portability and readability Cosm will be written in straight ANSI C. The ASM optimizations should be done in GNU-ASM for portability to all OS variants of a given chip. C++ Is nice, but has caused a great deal of strange porting problems in the past requiring the addition of many #define and #ifdef statements. Pointers are of unknown length so care must be taken not to do pointer math. The non-portable types int/long etc. should only be used as return values for pass/fail/error functions.

Testing

The Cosm_TestModule() functions must test all the functions in a file, causing all cases to be taken within the functions and all outcomes and errors to be generated. Cosm will also use the "well tested, infrequent release" philosophy, not "rapid release, let the user debug it" one. All code should always be kept in a state where it can be compiled without errors/warnings. Never submit code that you can't compile. The Cosm_TestModule() code will also serve as the example code, so it must be complete and correct.

Comments

Comments, comments, comments. In general anything more complex then a for( ... ) loop should have a comment in the code every few lines. Function definitions should also be documented (in the header file) with the parameters, return values, and error conditions. Any { } block longer then ~30 (1/2 page) lines should have a comment at the closing }

Header Files

Header files for a given C file should contain all defines, structs, and function prototypes for the code. Functions should appear in the .c file in the same order they appear in the .h file, the 'helper' functions (Cosm_*) after main interface functions (Cosm*). Defines should be used liberally, and constants directly in the code where the meaning isn't obvious (read: everywhere) should not be used. Any other non-Cosm header files that are needed for the .c file should be included at the top of the .c, not in the .h which should only include it's own information. Only if a .h is needed for the .h to parse should it be included from the .h.

Headers also contain the programmer documentation for a function, what it does, parameters, and expected return values or errors. No one should ever have to find and then dig around .c files to be able to use a function, and so any documentation should be in the header. All things will eventually be documented in non-programmer language in other formats.

Structure Alignment

All structures that will be written to the network or files, or used in an array MUST be properly aligned, padding if needed. This means that u64 and u128 data types must fall on 8 byte boundries, u32 on 4 byte boundries, and u16s on 2 byte boundries. The length of the structure must also be aligned for the biggest type, so if a struct has a u64, it has to be a multiple of 8 bytes long, etc. Becasue of this only platform specific structures that never leave memory (like a mutex) are immune to this requirement. The 2 defines PACKED_STRUCT_BEGIN and PACKED_STRUCT_END exist to insert the correct compiler commands to make a structure fully packed. Wrap any network or disk structure in these defines. See the sample code below for examples.

Version Control

Version numbers for any code or files must be incremented when any change is made. Problems caused by a non-incremented version number can be quite difficult to track. Luckily, Git does a wonderful job of this. Git also does a great job of tracking changes and history. Make sure your messages on commited code are descriptive. Do no use the top of a .c file for the history, it quickly takes over the entire file.

#ifdef Usage

System specific #ifdef are only allowed in CPU/OS level libraries. The only #ifdefs in the utility layer of code should be those provided by the CPU/OS layer for endian, 64biness, etc. When doing any test for CPU/OS type, use globally defined OS_TYPE and CPU_TYPE to switch off of e.g. #if ( OS_TYPE == OS_MACOSX ) - this is more readable to people not familiar with each compiler/os/cpu combination, and is less likely to lead to confusion. Also, preprocessore '#'s go in column 1, and never get indented due to some compilers being picky about the standard.

Naming Conventions

All names should make sense and be long enough for the code to be self documenting

Source File Names
Related code and header files should have the same base filename, e.g. file.c and file.h.

Functions
All functions should begin with a capital letter and each word within a function name should be capitalized, e.g. ReadConfig( char * filename );

Library Functions
All user level functions should begin with "Cosm", the module pneumonic, and each word within a function name should be capitalized, e.g. CosmFoo( ... ); All low level functions should begin with "Cosm_" and be named in a similar manner e.g. Cosm_Bar( ... );

Local Variables and Function Arguments
Loop variables can be one letter, e.g. u32 x, y, z; anything else should be descriptive and lower case. "_" should be used between words in local variables. e.g. u64 word_count;

Global Variables
Globals are variables that need an "extern" and you have to go running around files trying to find the definitions/usage. So don't even think about using them. No global variables exist in Cosm APIs, and only a couple are used for global initialization and shutdown. Users will need them however, so when we must use then, they start with "__" to avoid any chance of name collisions.

Structures
Structures should be named in all upper case with "cosm_" as a prefix e.g. struct cosm_PACKET_HEADER {...}; This may seem a bit odd until you realize it makes typing struct names very easy. This sets them apart from defines and macros. Structure members should be lower case like local variables.

Defines
Defines are in all upper case as per C standard e.g.
#define COSM_FOO 30
In general #if defined(...) is prefered over #ifdef since there is no #elifdef, so this is more consistent overall.

Macros
Macros are in all upper case beginning with "_COSM_" e.g.
#define _COSM_BAR( x ) Bar( x, 42 ); return;

Types and enums
All lowercase no underscores. e.g. u32, utf8char, cosmtime. Usage makes them clearly different from variables. The enum values should be named like defines, uppercase with "_".

Code Style

Liberal Spacing
DontCrunchTheCodeAllTogether( void ); e.g. for( i = 0 ; i < 42 ; i += 3 ) Each statement should be on it's own line. Statements that are long should be wrapped to the next line at the 78th column or less so that printing is possible. Any logic more then one line (for, while, if-else) should use a set of { } for clarity. funtions( x ) and macros( 3 ) do not have a space between the function name and firt paren. Typecasts (ascii *) also do not have the extra spaces to that they can be distinguished from parameters.

Indenting
2 spaces for every level of logic, and 2 additional spaces when a line is wrapped. Tabs should never be used, as they are editor dependant. When wrapping a line with a "||" or any other logical operator, the operator should start the second line, not end the first.

Width
Keep everything within the standard 78 columns, wrap lines when needed. Some of the code will need to be printed, snail-mailed, or OCR'd into other countries. There is one important exception to this, in the html documentation for functions the width of example code must be 70 columns or less, due how man pages are displayed.

Example:

-- blah.h --

#ifndef COSM_BLAH_H
#define COSM_BLAH_H

/* include everything we need */

#include "cputypes.h"
#include "cosm/os_io.h"

#define COSM_FOO_ERROR -3

typedef struct invalid_STRUCT
{
  u32 a;
  u64 b; /* not on 8 byte boundry, cannot be networked or filed */
  u16 c; /* length isn't a multiple of 8, cannot be arrayed */
} invalid_STRUCT;

PACKED_STRUCT_BEGIN
typedef struct valid_STRUCT
{
  u32 a;
  u16 c;
  u16 pad; /* padding */
  u64 b; /* correctly aligned, correct length, etc. */
} valid_STRUCT;
PACKED_STRUCT_END

s32 Cosm_TestBlah( void );

#endif

-- blah.c --

#include "blah.h"

s32 Cosm_TestBlah( void )
{
  s32 error;

  /*
    run all tests
    this is a multi-line comment
  */

  if ( ( /* test a fails */ )
    || ( /* line wraping */ ) )
  {
    error = COSM_FOO_ERROR;
  }
  else /* this is the else format */
  {
    error = COSM_PASS;
  }

  /* foo failed - this is single line comment */
  return error;
}

int CosmBlah( void )
{
  u128 apple;
  s32 e;

  /* use the macro to set apple */
  _COSM_SET128( apple, 0123456701234567, 89ABCDEF89ABCDEF )

  CosmPrint( "A is for Apple = 0x%032Z\n", apple );

  if ( foo = Cosm_TestBlah() );
  {
    CosmPrint( "Test Blah Failed = %i\n", foo );
    return COSM_FAIL;
  }

  return COSM_PASS;
}

indent
The unix program indent strips all whitespace and mangles comments but what comes close is an indent.pro file in docs/indent.pro. Since indent removes most all whitespace (a fundamental conflict with readability) this should only be used in absolute emergencies on extremely bad 3rd party code.

General Conventions

Readability
Code should be "readable" in a way that flows when read/spoken. This makes it easier to comprehend code when reading it for the first time, such as when hunting for a bug. This involves boith good variable naming, as well as the other formating and conventioned mentioned in this document. If the code does not read well when written, it will not read well later.

Return Values
When a pass/fail needs to be returned, zero should be pass, nonzero a failure. COSM_PASS and COSM_FAIL exist for this reason, so use them. This is so that all code can be of the general form

if ( COSM_PASS != ( error = test() ) )
{
  /* deal with the problem and recover */
}

When pointers are returned, NULL is failure, anything else should be a considered valid.

if ( NULL == ( pointer = test() ) )
{
  /* deal with the problem and recover */
}

When return is used, do not use the ()'s, it's not a function call.

int, char, short
int, char, short etc. are not used in the APIs. All parameters are of the universal cputypes.h formats. int can be used internally (with care) for any temp variables, for/while loops, system calls, etc.

Parameter order
When a function takes parameters, they should be (destination, source) similar to ( a = b ).

u64 and s64's
When using non-zero u64/s64 constants in code, use the "L" suffix. e.g. 42L, -1L. Do not use a lowercase "L", as it looks just like a "1"(one) in most fonts.

Additional keywords (for syntax highlighting):
u8 u16 u32 u64 u128 s8 s16 s32 s64 s128 f32 f64 f128 ascii utf8 utf8char cosmtime cosmid

Program names
Program names end in ".exe" reguardless of platform. Some platforms require this, the rest dont care. This way a program's name is always fixed.

Credit Where Credit is Due
A master CREDITS(.txt) file will be maintained where a list of names of the people that made a significant contribution to writing the code. People will be divided into categories as implementers, porters, and miscellaneous.

Common Pitfalls

Error Code Checking
All function error codes must be checked and acted on accordingly.

Network Buffers and Memory Overruns
Buffer overrun and bad memory allocation should always be checked for.

Use of Uncleared Structs
Structures need to be either CosmMemeAlloc'd or CosmMemSet to zero before use. Structures on the local stack will have random values when the function is entered.

Pointer Math
Never cast a pointer to a u32 or u64 to do pointer math. You do not know if the pointers will be 32 or 64bits wide on the destination chip, and there is no good reason to do pointer math outside of the ASM tweaked (and therefore CPU specific) cores.

NULL Pointers
Check for NULL pointers before all operations. Be especially careful of library functions i.e. memcpy() as many machines have libraries that have problems with NULLs.

Typecasting
Most things should be typecast as normal for ANSI C. However, s64, u64, s128, and u128 cannot be typecast because they are structures on some systems. For these types macro/functions are provided in os_math.

Memory Leaks
Always Make sure any memory allocation has a matching memory free.


© Mithral Inc. 1995-2024. All Rights Reserved.
Mithral® and Cosm® are trademarks of Mithral Inc.