- About
- License
- Git Commit Style Guide
- C Style Guide
- C++ Style Guide
- Swift Style Guide
- C# Style Guide
- Objective-C Style Guide
- x86 Assembly Style Guide
These are the coding conventions used for all recent XS-Labs projects.
Feel free to comment, raise issues, fork and/or adapt this document.
XS-Labs' coding conventions make a heavy use of whitespaces.
It's not a common habit among coders, who usually prefers compact code.
This is just a personal taste.
After years of professional development, I like to see a lot of spaces in my own code. It's like letting the code breath.
Code is about rhythm, and readability. And I think taking time to carefully align operators, declarations, etc. improves the overall readability and the feeling you can get while reading code.
This style guide is published under the terms of the FreeBSD documentation license.
Always follow the Conventional Commits specification for Git commit messages:
https://www.conventionalcommits.org/en/v1.0.0/
Allowed commit types are:
fix
feat
chore
style
refactor
build
test
ci
revert
- Indentation
- Ending line
- Comments
- Maximum number of columns
- Includes
- Whitespace
- Braces
- Alignment
- Case and symbols naming
- Variable declaration
- Macros
- Structures and unions
- Enumerated types
- Typedefs
- New lines
- Header guards
- Functions prototypes
- Functions with no parameters
- Inline functions
- Dereferencing
- Conditionals
- Switch statements
- Long
if/else if
statements - C++ compatibility
- Inline documentation
- Compilation
Code should always be indented using four spaces. Never use tabulations for indentation.
Preprocessor directives should also be indented:
void foo( void )
{
#ifdef FOO
/* ... */
#endif
}
Not:
void foo( void )
{
#ifdef FOO
/* ... */
#endif
}
Source and header files should always end with a single empty line.
Comments should always use the /* */
notation.
Single line C++ style comments (//
) are strictly prohibited.
A line of comment should whenever possible be no more that 80 columns.
When a comment consists of a single line, place the /* */
on the same line.
If the comments consists of multiple lines, place the /* */
on a new line:
/* Single line comment */
/*
* Multiple
* line
* comment
*/
When using multiple line comments, always align the *
signs, as in the above example.
The number of columns for a single line is not limited.
However, try whenever possible to wrap long lines in order to improve the overall readability.
Include directives should always come first, before any other declaration:
#include <stdio.h>
int x;
Not:
int x;
#include <stdio.h>
An exception is made when a header needs a specific macro to be set before inclusion:
#define FOOBAR 1 /* Permitted */
#include <foobar.h>
A single whitespace character should always be used around all operators except unary operators:
x = 1 + 2 + 3;
x++;
~x;
if( !x )
{
/* .... */
}
Not:
x=1+2+3;
x ++;
~ x;
if( ! x )
{
/* .... */
}
A single whitespace character should always be used inside parenthesis and brackets, but never before:
x[ 0 ] = 0;
foo( x );
if( y == 0 )
{
/* */
}
Not:
x[0] = 0;
foo (x);
if (y == 0)
{
/* */
}
The pointer sign should alway have a leading and trailing space:
int * x;
Not:
int* x;
int *x;
No whitespace should be added after a cast. A single whitespace should be used after the opening parenthesis and before the closing one:
x = ( char * )y;
Not:
x = ( char * ) y;
When using for
loops, a single whitespace character should be used after the semicolons:
for( i = 0; i < 10; i++ )
{
/* ... */
}
Not:
for( i = 0;i < 10;i++ )
{
/* ... */
}
Braces should always be placed on an empty line.
This apply for all constructs (functions, conditions, loops, etc.).
Code inside braces should be indented by four spaces:
void foo( void )
{
if( ... )
{
/* ... */
}
else if( ... )
{
/* ... */
}
else
{
/* ... */
}
for( ... )
{
/* ... */
}
while( ... )
{
/* ... */
}
do
{
/* ... */
}
while( ... );
}
An exceptions can be made for very simple constructs:
if( ... ) { x = 1; }
else if( ... ) { x = 2; }
Always align consecutive assignments:
x = 1;
foo = 2;
foobar += 2;
Not:
x = 1;
foo = 2;
foobar += 2;
If using multiple lines in an assignment, aligns the extra lines to the equal sign:
x = 1;
foobar = x
+ 1
+ 2;
When using conditional assignment, aligns the ?
and :
signs whenever possible.
The ?
sign should be aligned by adding whitespaces before the closing parenthesis:
x = 1;
foobar = ( x ) ? 2 : x + 3;
foo = ( foobar ) ? foobar : x;
Not:
x = 1;
foobar = ( x ) ? 2 : x + 3;
foo = ( foobar ) ? foobar : x;
Always aligns the names of variables:
int x;
unsigned long y;
float z;
Not:
int x;
unsigned long y;
float z;
When using pointers, place the pointer sign next to the variable name:
int * x;
unsigned long * y;
If using single line conditional statements (see above), align the if
/else
statements, as well as the opening/closing braces and comparison operators:
if( x == 1 ) { foobar = 1; }
else if( foobar == 1 ) { x = 0xFFFFFFFF; }
else { x = 0; }
Not:
if( x == 1 ) { foobar = 1; }
else if( foobar == 1 ) { x = 0xFFFFFFFF; }
else { x = 0; }
Always align the closing brackets when using the array subscripting operator. Indexes should be indented in a logical manner:
x[ 1 ] = 0;
x[ 100 ] = 0;
Not:
x[ 1 ] = 0;
x[ 100 ] = 0;
Local variables should never start with an underscore, and should always start with a lowercase letter.
Lower camel-case is recommended.
For global symbols (variables and functions), upper camel-case is usually recommended.
Underscores may be used to simulate namespaces.
void SomePackage_SomePublicFunction( void );
extern int SomeInteger;
Symbols shall never start with two underscores, or with an underscore followed by an uppercase letter.
Local variables should be declared before any other statement:
void foo( void )
{
int x = 0;
int y = 1;
foo();
foobar();
}
Not:
void foo( void )
{
bar();
int x = 0;
foobar();
int y = 0;
}
If you need to delcare variables after a statement, always use a dedicated scope:
void foo( void )
{
int x = 0;
foo();
{
int y = 1;
foobar();
}
}
The rules above do not apply to for
loops, where a variable delcaration is permitted and recommended:
for( int i = 0; i < 10; i++ )
{
/* ... */
}
Not:
int i;
for( i = 0; i < 10; i++ )
{
/* ... */
}
Macros should always be in uppercase.
#define FOO 1
If a macro takes parameters, the parameters names should begin and end with a single underscore:
#define BAR( _x_ ) ( ( _x_ ) + 1 )
As in the above example, parenthesis should always be used around a macro parameter.
Members of structures and unions should be properly aligned, as mentioned before:
struct foo
{
int x;
unsigned long y;
};
union bar
{
int x;
unsigned long y;
};
When manually padding a struct, use a leading underscore for the member name, and a trailing number, prefixed with a single underscore.
Always use a char
array to manually pad a structure:
struct foo
{
char s;
char _pad_0[ 3 ];
int x;
};
Enum values should be properly aligned, as mentioned before.
If using explicit values, hexadecimal notation is preferred:
enum
{
Foo = 0x01,
Bar = 0x02,
Foobar = 0x10
};
Not:
enum
{
Foo,
Bar,
Foobar = 0x10
};
When using flags, the left shift operator is recommended:
enum
{
Foo = 1 << 0,
Bar = 1 << 1,
Foobar = 1 << 2
};
Simple typedefs are declared on a single line:
typedef int Foo;
With structures, unions and enumrated types place the type name on a new line:
typedef struct
{
int x;
int y;
}
Foo;
For enumrated types, each value should be prefixed by the type name:
typedef enum
{
FooX = 0,
FooY = 1,
FooZ = 2
}
Foo;
Not:
typedef enum
{
X = 0,
Y = 1,
Z = 2
}
Foo;
An empty line should be used to separate logical parts of the code, as well as to separate function calls and assignments:
x = 0;
y = 0;
foo();
z = 2;
Not:
x = 0;
y = 0;
foo();
y = 2;
All headers should be properly guarded:
#ifndef FOO_H
#define FOO_H
/* ... */
#endif /* FOO_H */
The name of the macro used as header guard should always be in uppercase.
It should consist of the name of the header file (optionally with directories prefixes, separated by a single underscore), and a trailing _H
.
For instance, for include/foo/bar/foobar.h
, this should be FOO_BAR_FOOBAR_H
.
Function prototypes should always declare the parameters names.
A single whitespace character should be used after the coma separating parameters.
The return type should be place on the same line as the function's name:
void foo( int x, int y );
Not:
void
foo( int,int );
Functions without parameters should always be declared as taking void
:
void foo( void );
Not:
void foo();
Inline functions should generally be avoided, unless there's a very good and specific reason to make them inline.
When using the dereference operator *
, always use an extra set of parenthesis:
*( x ) = 1;
y = *( x );
Not:
*x = 1;
y = *x;
Always use braces with conditionals:
if( x == 1 )
{
x = 2;
}
Not:
if( x == 1 )
x = 2;
Don't use else
clauses when not necessary:
if( x == 0 )
{
return true;
}
return false;
Not:
if( x == 0 )
{
return true;
}
else
{
return false;
}
Also avoid using !
in a conditional:
if( b == false || p == null )
{
/* ... */
}
Not:
if( !b || !p )
{
/* ... */
}
When using switch statements, separate each case
with an empty line and adds an empty line after the case
.
The break
statement should be indented, in regard to the case
statement.
switch( x )
{
case 1:
/* ... */
break;
default:
/* ... */
break;
}
An exception can be made for very simple switch statements:
switch( x )
{
case 1: y = 0; break;
default: foobar = 0xFFFFFFFF; break;
}
In such a case, break
statements should be aligned, as well as assignment operators, if any.
Very long if
/else if
statements should be wrapped the following way:
if
(
x == 1
&& y == 2
&& foobar == 3
)
{
/* ... */
}
Parenthesis are placed on a new line. Variable names and comparison operators should be aligned.
When using extra parenthesis, apply the same rules:
if
(
x == 1
&& y == 2
&&
(
z == 3
|| foobar == 4
)
)
{
/* ... */
}
All headers should be compatible with C++, using extern "C"
:
#ifdef __cplusplus
extern "C" {
#endif
/* ... */
#ifdef __cplusplus
}
#endif
Documented code should prefer Apple's HeaderDoc syntax rather than JavaDoc.
Always compiles your code with -Werror
or similar, and always use the highest possible error reporting level.
When function parameters are not used, cast them to void
to prevent a warning:
void foo( int unused )
{
( void )unused;
}
All rules from the C Style Guide applies here, with a few exceptions and additions described hereafter.
- Files and files extensions
- Comments
- Namespaces
- Classes 1. Naming 2. Inheritance 3. Members 4. Method definitions 5. Destructors
- Templates
- Using
- Variable declaration
- Function/method arguments
- Functions with no parameters
- Lambdas
- Enumerated types
- Includes and forward declarations
- Using C functions
C++ source files should end with the .cpp
extension.
C++ headers should end with the .hpp
extension.
Source and header files for classes should be named as the classes they declare/define.
Namespaces should be represented as directories.
Single line C++ comments are allowed, even if the /* */
notation is usually preferred, especially when the comment consists of multiple lines.
Namespaces should always follow the upper camel-case rule.
C++ classes should always follow the upper camel-case rule.
Properties should always follow the lower camel-case rule.
Methods should follow either the upper camel-case rule or the lower camel-case rule, but mixing both in the same project is not allowed.
Private members should always have a leading underscore.
The :
sign should immediately follow the class name and should be followed by a single whitespace character.
When inheriting from multiple classes, a single whitespace character should be used after the comma:
class FooBar: Foo, Bar
{
};
Public members should be declared first, followed by protected and private members.
Always group members with the same visibility (properties and methods), unless it is not possible.
Static members should also be declared first, inside the visibility group, followed by methods and properties.
Separate static methods, methods and properties by an empty line.
The public
, protected
and private
keywords should be indented by four spaces and an empty line should be placed directly after.
Members should be indented by four more spaces:
class Foo
{
public:
static void staticMethod();
Foo();
int x;
unsigned long y;
private:
void _bar();
int _z;
};
Except for templates, method should never be defined in the header files.
Destructors should always be declared as virtual
, unless there's a very good and specific reason not to do so.
Template parameters should be surrounded by a single whitespace character.
A single whitespace character should be used after the comma.
The <
sign should immediately follow the class name:
template class Foo< int x, int y >
{
};
The using
keyword is usually discouraged for namespaces, except for very long namespaces.
It's strictly prohibited for the std
namespace - the full notation should always be used.
std::vector< int > v;
Not:
using std;
vector< int > v;
Always declare variables with a value.
Use either parenthesis or the equal sign.
int x( 0 );
Or:
int x = 0;
Using braces is also allowed.
In such a case, always add a leading space before the opening brace:
int x {};
int y { 42 };
Not:
int x{};
int y{ 42 };
Variables declarations and statement may be mixed in C++.
In such a case, always separate variables declarations and statements with an empty line:
int x = 0;
Foo::Bar();
std::string s = "hello, world";
Except for out parameters, primitive or integral types should always be passed by value.
For other types, passing const
references is preferred:
void foo( int value );
void bar( const std::string & value );
Over:
void foo( const int & value );
void bar( std::string value );
Unlike C conventions, empty parenthesis are fine and preferred for functions or methods without parameters:
void foo();
When using lambdas, a single space should be placed before and after any capture.
A single space should be placed after the comma in the capture list.
When there is no capture, no space should be placed inside the brackets:
auto a = []() {};
auto b = [ & ]() {};
auto c = [ this ]() {};
auto c = [ this, foo ]() {};
Not:
auto a = [ ]() {};
auto b = [&]() {};
auto c = [this]() {};
auto c = [this,foo]() {};
No space should be placed between the capture brackets and the opening parenthesis for arguments.
auto a = []( int x ) {};
auto b = []() {};
Not:
auto a = [] (int x) {};
auto b = [] {};
The return type may be omitted.
If specified, a leading and trailing space should be placed around ->
:
auto a = []() -> int {};
Not:
auto a = []()->int {};
Scoped enumerations should always be preferred over unscoped enumerations:
enum class Foo
{
X
Y
};
Over:
enum Foo
{
FooX
FooY
};
The number of included files contained in the headers should be limited.
Always use forward declarations when possible:
class Foo;
class Bar
{
public:
Bar( Foo * f );
};
Not:
#include "Foo.h"
class Bar
{
public:
Bar( Foo * f );
};
The use of C functions is generally discouraged unless there's no C++ equivalent, or if there's a specific reason not to use a C++ equivalent.
C library headers should not be used directly. Always use the C++ variants instead, when available:
#include <cstdio>
Not:
#include <stdio.h>
... Work in progress ...
... Work in progress ...
All rules from the C Style Guide applies here, with a few exceptions and additions described hereafter.
- Files
- Primitive datatypes
- Bracket notation
- Literals
- Constants
- Enumerated types
NULL
,nil
andNil
- Blocks
- Classes
- Singletons/Shared instances
- NSLog
- Multithreading
- Compilation
Source and header files for classes should be named as the classes they declare/define.
Directories should be used to separate logical groups of source files.
For categories, the source and header files should be name as the class, followed by a +
and the category name (SomeClass+SomeCategory.m
).
Unless there's a specific need to do so, never use the C primitive datatypes.
The Objective-C equivalent should always be preferred:
NSInteger
instead ofint
orlong
NSUInteger
instead ofunsigned int
orunsigned long
CGFloat
instead offloat
ordouble
Or use the types from stdint.h
when needed.
When sending messages, a single whitespace should be used before and after the brackets:
a = [ [ NSArray alloc ] init ];
Not:
a = [[NSArray alloc]init];
Long lines should be wrapped the following way:
a = [ [ NSDictionary alloc ] initWithObjects: obj1,
obj2,
obj3,
nil
forKeys: @"key1",
@"key2",
@"key3",
nil
];
The closing bracket is aligned with the opening one.
Parts of the method name are aligned to the left, as well as arguments.
The use of literals is generally encouraged, as well as subscripting:
array = @[ obj1, obj2 ];
dictionary = @{ @"key1" : obj1, @"key2" : obj2 };
number = @42;
Instead of:
array = [ NSArray arrayWithObjects: obj1, obj2, nil ];
dictionary = [ NSDictionary dictionaryWithObjectsAndKeys: obj1, @"key1", obj2, @"key2", nil ];
number = [ NSNumber numberWithInteger: 42 ];
Long declarations for array or dictionaries literals should be wrapped the following way:
array = @[
obj1,
obj2
];
dictionary = @{
@"key1" : obj1,
@"key2" : obj2,
@"foobar" : obj2
};
The opening and closing brackets/braces are aligned.
Contained objects are placed on their own line, indented by four spaces.
Fo dictionaries, the :
signs are aligned.
The use of the k
prefix for constants is prohibited.
When related to a class, constants should be prefixed by the class name.
The name of constants should follow the upper camel-case rule.
The use of the extern
keyword is strictly prohibited for constants. Use the FOUNDATION_EXPORT
macro instead:
FOUNDATION_EXPORT NSString * const SomeClassConstantName;
Not
extern NSString * const kConstantName;
The use of the NSEnum
and NSOptions
macros is encouraged, while not mandatory.
NULL
, nil
and Nil
should not be used interchangeably.
nil
should always be used for instances, while Nil
should be used for classes.
For any other pointer type, NULL
should be used.
The declaration of a block should follow the same rules as the declaration of a function pointer:
NSUInteger ( ^ blockName )( NSUInteger x );
The ^
sign is surrounded by a single whitespace character.
For complex blocks, a typedef is recommended:
typedef NSUInteger ( ^ blockTypeName )( NSUInteger x );
The definition of blocks should follow the function's definition style.
No whitespace should be placed after the ^
sign.
Block's code should be indented by four spaces.
block = ^( void )
{
/* ... */
};
Blocks without arguments should always be defined as taking void
.
- Naming
- Interface declaration
- Instance variables
- Properties
- Properties atomicity
- Methods
- Imports and forward declarations
- Private methods
- Categories
- Protocols
The name of classes should follow the upper camel-case rule.
The interface declaration should follow the following rules:
Instance variabes are prohibited in the interface (see 9.3. Instance variables).
Properties and methods should not be indented.
Properties should come first, followed by methods.
The :
sign after the class name should have a trailing whitespace character, but never a leading one.
If protocols are implemented, the opening <
sign should have a leading and trailing whitespace character.
The closing >
sign should have a leading whitespace character.
A single whitespace character should be placed after the comma, when implementing multiple protocols.
Example:
@interface Foobar: NSObject < Foo, Bar >
@property( atomic, readonly ) NSInteger x;
- ( void )foo;
@end
Instance variables should follow the lower camel-case rule and start with a single leading underscore.
Using instance variables should be avoided avoided whenever possible.
Use properties instead.
Instance variables are not allowed in the public interface.
Use a private class extension in the implementation instead.
Except when using headerdoc comments, the name of the instance variables should be aligned, as mentioned in the alignment topic of the C style guide:
@interface Foo()
{
NSUInteger _x;
NSArray * _array;
NSDictionary * _dict;
}
Not:
@interface Foo()
{
NSUInteger _x;
NSArray * _array;
NSDictionary * _dict;
}
Properties variables should follow the lower camel-case rule.
Properties should always declare their full attributes:
@property( nonatomic, readwrite, assign ) NSUInteger x;
Not:
@property( assign ) NSUInteger x;
Properties should generally be declared as atomic
, unless there's a specific reason not to do so.
An exception is made for IBOutlet
properties, which should always be nonatomic
.
Methods should follow the lower camel-case rule.
The use of a leading underscore is strictly prohibited.
Empty parameter names are discouraged.
A trailing whitespace should be used after the +
or -
sign.
The method's return type and argument's types should follow the same rule as casts, as mentioned in the C style guide.
A trailing whitespace character should be used after the :
sign, but not before:
- ( void )methodWithObject1: ( id )object object2: ( id )object;
Not:
-( void ) methodWithObject1 :( id ) object object2 :( id ) object;
Parameter names should be as explicit as possible.
The use of a the
or a
prefix for a parameter name is strictly prohibited.
( id )object
Not:
( id )anObject
The number of included files contained in the headers should be limited.
Always use forward declarations when possible:
@class Foo;
@interface Bar
- ( id )initWithFoo: ( Foo * )foo;
@end
Not:
#import "Foo.h"
@interface Bar
- ( id )initWithFoo: ( Foo * )foo;
@end
Private methods should be declared and defined in a class extension, in the main implementation.
Declaration may be avoided, but is usually preferred:
#import "Foo.h"
@interface Foo()
- ( void )bar;
@end
@implementation Foo()
- ( void )bar
{}
@end
Each category should have its own header and source file.
Declaration should be as follow, with a single whitespace character around the category name, and no leading whitespace before the (
sign:
@interface Foo( CategoryName )
Not:
@interface Foo (CategoryName)
If the protocol conformance is not intended to be public, the main interface file should not declare it.
For instance, a Foo
class implementing NSTableViewDelegate
(for internal use).
Foo.h
:
@interface Foo: NSObject
@end
Foo.m
:
#import "Foo.h"
@interface Foo() < NSTableViewDelegate >
@end
Singletons or shared instances should always be created with the dispatch_once
pattern.
For pure singletons, allocWithZone:
should be overriden to return the shared instance:
+ ( instancetype )sharedInstance
{
static dispatch_once_t once;
static id instance = nil;
dispatch_once
(
&once,
^( void )
{
instance = [ [ super allocWithZone: nil ] init ];
}
);
return instance;
}
+ ( id )allocWithZone: ( NSZone * )zone
{
( void )zone;
return [ self sharedInstance ];
}
NSLog
should be used carefully.
It's recommended to use a macro to disable logging for release builds.
The use of libdispatch
should always be preferred to standard NSThread
approach, unless there's a specific reason to use NSThread
, like ensuring the thread is detached immediately.
GCC should never be used to compile Objective-C. Use Clang/LLVM instead.
- Syntax
- Local labels
- Indentation
- Alignment
- Whitespace
- Comments
- Grouping
- Procedures comments
- Optimisation
The Intel syntax should always be preferred, when possible, to the AT&T syntax, for clarity.
An exception is made for inline assembly, when compiling C code with Clang or GCC.
The basic differences are the following:
The direction of the operands in the Intel syntax is the opposite of AT&T syntax.
In the Intel syntax, the destination operand comes first, followed by the source operand:
instr dest, src ; Intel
instr src, dest # AT&T
The Intel syntax doesn't use prefixes for register names or immediate operands, while the AT&T syntax uses the %
prefix for registers and $
for immediate operands:
mov rax, 1 ; Intel
movq $1, %rax # AT&T
The Intel syntax doesn't use suffixes for mnemonics, while the AT&T syntax uses b
, w
, l
and q
.
The size of the operands is automatically assumed when using registers.
For memory operands, similar directives can be used (BYTE
, WORD
, DWORD
, QWORD
):
; Intel
mov rax, 1
mov eax, 1
# AT&T
movq $1, %rax
movl $1, %eax
The Intel syntax uses []
for memory operands, while the AT&T syntax uses ()
:
mov rax, [ rdi + 8 ] ; Intel
movq 8( %rdi ), %rax # AT&T
The index, scale, displacement and segment also use a different notation:
mov rax, segment:[ base + index * scale + displacement ] ; Intel
movq %segment:displacement( base, index, scale ), %rax # AT&T
Local labels inside a procedure should always start with a dot.
For instance:
procedure:
.label1:
; ...
.label2:
; ...
ret
Label names should be meaningful.
Don't use compiler style label names, like L1:
.
Code should always be indented using four spaces. Never use tabulations for indentation.
Mnemonics and operands should be aligned, in order to improve the code's readability, and a decent amount of spaces should be placed between the mnemonics and the operands:
xor rax, rax
mov al, 1
pxor xmm0, xmm0
movdqa xmm1, [ rsi ]
movdqa [ rdi ], xmm1
Not this:
xor rax, rax
mov al, 1
pxor xmm0, xmm0
movdqa xmm1, [rsi]
movdqa [rdi], xmm1
When using memory operands, inserts a white space between the brackets:
mov rax, [ rdi ]
Not:
mov rax, [rdi]
Also inserts a a whitespace around arithmetic operators:
mov rax, [ rdi + 8 ]
Not:
mov rax, [rdi+8]
Comments should be placed on a new line and should not exceed 80 columns in width:
; This is a comment
; with another line...
xor rax, rax
Not:
xor rax, rax ; This is a comment
Comments should be as meaningful as possible.
Don't simply describe what you are doing, but also why you are doing it.
Instructions should be grouped in a logical manner, with a newline between groups:
; Comment for instruction group 1
xor rax, rax
xor rcx, rcx
mov al, 2
mov cl, 8
mul rcx
; Comment for instruction group 2
pxor xmm0, xmm0
movdqa xmm1, [ rdi ]
All procedures should start with a standard comment, describing the procedure, the input and return registers, as well as killed registers, if any:
;-------------------------------------------------------------------------------
; Short description of the procedure (single line)
;
; Long description of the procedure
; (can be multi-line)
;
; Input registers:
;
; - RDI: Parameter description
; - RSI: Parameter description
;
; Return registers:
;
; - RAX: Return value description
;
; Killed registers:
;
; - RCX
; - RDX
;-------------------------------------------------------------------------------
When no register is used as input or as return, or when no register is killed:
;-------------------------------------------------------------------------------
; Short description of the procedure (single line)
;
; Long description of the procedure
; (can be multi-line)
;
; Input registers:
;
; None
;
; Return registers:
;
; None
;
; Killed registers:
;
; None
;-------------------------------------------------------------------------------
The following is not mandatory, but it's strongly advised to follow these recommendations, when writing performance-critical code.
Always use xor
to zero a register, instead of mov
:
xor rax, rax
Not:
mov rax, 0
Always use test
when comparing with zero, instead of cmp
:
test rax, rax
jz .label
Not:
cmp rax, 0
je .label
Always use add
and sub
when incrementing or decrementing a register, instead of inc
or dec
:
add rax, 1
sub rbx, 1
Not:
inc rax
dec rbx
While inc
and dec
are shorter, some processors have performance issues with those mnemonics.
When using conditional jumps, the following rules should be observed in order to increase the overall performances (due to the CPUs branch prediction algorithm).
test rax, rax
jz .label
; Fallthrough - Most likely
.label:
; Forward branch - Most unlikely
.label:
; Backward branch - Most likely
test rax, rax
jz .label
; Fallthrough - Most unlikely
And of course, eliminate branches whenever possible.
Whenever possible, unroll loops:
.loop:
mov [ rdi ], [ rsi ]
mov [ rdi + 8 ], [ rsi + 8 ]
mov [ rdi + 16 ], [ rsi + 16 ]
mov [ rdi + 24 ], [ rsi + 24 ]
add rdi, 32
add rsi, 32
sub rcx, 32
test rcx, rcx
jnz .loop
Instead of:
.loop:
mov [ rdi ], [ rsi ]
add rdi, 8
add rsi, 8
sub rcx, 8
test rcx, rcx
jnz .loop
Owner: Jean-David Gadina - XS-Labs
Web: www.xs-labs.com
Blog: www.noxeos.com
Twitter: @macmade
GitHub: github.com/macmade
LinkedIn: ch.linkedin.com/in/macmade/
StackOverflow: stackoverflow.com/users/182676/macmade