1) Introduction
2) Compilation
3) Using fmars
4) Redcode assembler
5) Python (SWIG) interface
6) Known issues and contact information

==========


1) Introduction

fmars is Fast Memory Array Redcode Simulator - a specialized simulator
for the game of Corewars. It's designed to be of particular use in automated
redcode optimizers and evolvers. fmars borrows the idea from Martin Ankerl's
qmars and pushes it to the extreme - it generates source code with special
case for every possible opcode/addressing mode combination. This allows
some optimizations that aren't possible in other simulators. fmars is
compatible with pMARS with an exception for p-space, which is not yet
implemented.

==========


2) Compilation

To build fmars, you will need GCC (preferrably >= 3.2) and Guile 1.6
(Scheme interpreter). It should be possible to compile fmars with a different
compiler, but probably (at least for Intel's compiler) the performance will
be much worse, and some of optimizations rely on GCC extensions.


The code generator takes a list of instructions as an input, and produces
a simulator which will be able to run warriors consisting of these
instructions. Trying to run a warrior that contains an instruction that
wasn't included in generator's input will cause the simulator to report an
error.

The generator can read the list of instructions either from a warrior
assembled by pMARS, or from a file containing lines in the form:

opcode.modifer a_addressing_mode b_addressing_mode

The make variable INSN_FILES controls which files will be parsed, and is
set to 'fsh.set' by default. So, to build a mars that will supports every
warrior from Fixed Strategy Hill, and additionally a warrior 'war.red',
issue the following command:

make INSN_FILES="fsh.set war.red"

where 'war.red' is a warrior preprocessed by pMARS or fmars parser. This
should result the generation of the object file 'fm_sim.o' and the header
file 'fm_types.h'. Then it should be possible to link the mars with your
program. Note that header files 'insn.h' and 'exhaust.h' slightly differ
from the ones from original exhaust.


The quality of generated code is controlled by configuration file
'fmars.cfg'. It includes a number of optimization options, some of which
are processor-specific. The configuration file contains more details.


IMPORTANT

When generated with certain configuration options, the code violates
C aliasing rules. That may sometimes result in a bad output from the
simulator. The only reason for keeping such code is that it happens to
work faster (with GCC 3.3). It depends on the compiler if it will work
correctly and how much faster.

If you decide to use such code, you should test it for correctness. You
may find autotest.py useful for detecting wrong behavior of mars. If it
happens, you have several options:

- use -fno-strict-aliasing option of GCC
- enable 'union' switch in configuration file
- disable the offending configuration option

Every solution leads to a certain slowdown. And it is hard to tell which one
is best - you should check this for yourself.

==========


3) Using fmars

In order to use fmars in your programs, you should perform following steps:

- allocate memory for mars
- load warriors into exhaust format (possibly using fmars parser)
- convert warriors from exhaust format into fmars internal format
- for each round:
   - clear the core
   - load warriors (in internal format) into the core
   - call the simulator
- free allocated memory (both simulator and warriors)


Example usage

To allocate memory for the simulator:

   #include "fmars.h"

   int nwarriors = 2;
   int coresize = 8000;
   int processes = 8000;
   int cycles = 80000;
   int pspacesize = 500;
   int maxlength = 100;
   int rounds = 200;

   fmars_mars_t *mars;

   mars = fmars_alloc (nwarriors, coresize, processes, cycles, pspacesize);
   if (mars == NULL)
      panic ("Failed to allocate memory");


In order to interact with the world, fmars uses (slightly modified) exhaust
warrior format. To get a warrior in this format using fmars parser, you
should do somethin like this (for simplicity, the example code doesn't
check for errors and buffer overflows other than related to fmars):

   #include "fm_asm.h"

   int err;
   char buf[16384];
   warrior_t *exhaust_w1, *exhaust_w2;

   /* read the file into a string */

   size = read (open ("aeka.rc", O_RDONLY), buf, sizeof (buf));
   buf[size] = 0;

   /* call the parser; it will return 0 on success or error in case of an
      error; arguments set to -1 in this example are used only for predefines
      in the assembler */
   
   err = fm_asm_string (buf, NULL, &exhaust_w1,
                        nwarriors, coresize, -1, -1, -1, maxlength, -1, -1);
   if (err)
       panic ("Assembler error");

   [repeat for the second warrior]


The Exhaust format is convenient for transportation purposes, but for sake of
performance, fmars uses different format internally. The internal
representation of the warrior is tightly bound to the allocated mars, which
means you cannot reuse the same warrior in different instances of mars.

    fmars_warrior_t *w1, *w2;
    
    w1 = fmars_bind_warrior (mars, exhaust_w1);
    free (exhaust_w1->code);
    free (exhaust_w1);

    w2 = fmars_bind_warrior (mars, exhaust_w2);
    free (exhaust_w2->code);
    free (exhaust_w2);
    
    if (w1 == NULL || w2 == NULL)
        panic ("Failed to convert warriors");


Now, we need to clear the core and load the warriors into it. The third
argument of fmars_load_warrior() is warrior's unique ID ranging from
0 to (nwarr - 1), which shouldn't be changed if the pspace is being used.
The fourth argument is position in the core to load the warrior, and
the fifth argument is warrior's position in the starting seqence,
also ranging from 0 to (nwarr - 1).

   int wins[]   = {0, 0};
   int losses[] = {0, 0};
   int ties[]   = {0, 0};

   for (round = 0; round < rounds; round++)
   {
      fmars_clear_core (mars);
      fmars_load_warrior (mars, w1, 0, 0, round % 2);
      fmars_load_warrior (mars, w2, 1, 100 + random () % 7801, 1 - round % 2);


Now we're ready to call the simulator. The return value will contain number
of warriors that survived (or -1 in case of simulator panic), and death_tab
will contain IDs of dead warriors, in order in which they were killed:

      [still in for loop]

      int alive, *death_tab;

      alive = fmars_sim_multiwarrior (mars, &death_tab);
      if (alive < 0)
         panic ("Simulator panic (unsupported instruction?)");
      else if (alive == 2)
      {
         ties[0]++;
         ties[1]++;
      }
      else if (death_tab[0] == 0)
      {
         /* warrior with ID 0 lost */
         losses[0]++;
         wins[1]++;
      }
      else
      {
         wins[0]++;
         losses[1]++;
      }
   } /* for */
  

When we're done, we can free previously allocated memory:

   fmars_free_warrior (w1);
   fmars_free_warrior (w2);
   fmars_free (mars);


For more details check header files 'fmars.h' and 'fm_asm.h'.

==========


4) Redcode assembler

fmars includes an easy to embed redcode parser. It is compatible with pMARS
assembler, with following exceptions:

- there are no arithmetic registers
- the comments, including preprocessor directives, are not parsed
- FOR/ROF loops are not allowed in EQUs
- EQUs other than arithmetic expressions must be declared before they
  are used
- EQUs are not expanded when instruction modifier is expected (after dot)


The assembler is designed to deal with correct input. It is a bit more
allowing than pMARS (for example, you can redefine labels and EQUs),
and gives almost no feedback on errors.


5) Python interface

fmars also includes rudimentary bindings for languages supported by
SWIG. The makefile only supports python binding, but adding other
languages should be trivial (see SWIG manual). You should be able
to figure out how to use these bindings by looking at sample application,
pymars.py (it's really simple).


==========


6) Known problems, bugs and future work

- the Makefile is currently broken (just 'make clean' in case of problems).
- MMX code works only for short ints as core field type
- MMX didn't work under windows the last time I checkd
- the simulator may not work when coresize is greater than maximal
  value of a field type


For future releases, I'm planning to implement p-space, clean up code a bit,
include more friendly API for simulator and assembler, and maybe a client and
a server for distributed computation.


If you stumble on any unexpected behavior which wasn't mentioned in this
file, please send me an e-mail to the address below. I'd appreciate a simple
test case if possible.


The author can be reached at this address:
janeczek@gmail.com
