/*objasm.c - output an objasm file given a module defn*/

/*OSLib---efficient, type-safe, transparent, extensible,\n"
   register-safe A P I coverage of RISC O S*/
/*Copyright � 1994 Jonathan Coxhead*/

/*
      OSLib 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 1, or (at your option)
   any later version.

      OSLib 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 programme; if not, write to the Free Software
   Foundation, Inc, 675 Mass Ave, Cambridge, MA 02139, U S A.
*/

/*From CLib*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/*From OSLib*/
#include "os.h"
#include "osfile.h"

/*From Support*/
#include "x.h"
#include "lookup.h"
#include "trace.h"

/*Local*/
#include "def.h"
#include "main.h"
#include "objasm.h"

#include "VersionNum"

#define MEM_FOR_OBJASM (1024*1024)

#ifdef EXECUTE_ON_UNIX
#  define ASMCMD "armasm"
#else
#  define ASMCMD "ObjAsm"
#endif

static char *Local;

static char
   *APCS_Names [] =
   /*names of registers as seen by A P C S*/
   {  /*arguments*/
      "A1", "A2", "A3", "A4",

      /*variables*/
      "V1", "V2", "V3", "V4", "V5", "V6",

      /*fixed purpose*/
      "SL", "FP", "IP", "SP", "LR", "PC"
   },

   *SWI_Names [] =
   /*names of registers as seen by SWI call*/
   {  /*general*/
      "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9",

      /*fixed purpose*/
      "SL", "FP", "IP", "SP", "LR", "PC"
   };
/*-----------------------------------------------------------------------*/

static char *Op
(
   def_op op
)
{
   switch (op)
   {
      case def_OP_DISJOINS:
         return "ORR";
      break;

      case def_OP_CONJOINS:
         return "AND";
      break;

      case def_OP_ADDS:
         return "ADD";
      break;

      case def_OP_EXCLUSIVELY_DISJOINS:
         return "EOR";
      break;
   }

   return SKIP;
}
/*-----------------------------------------------------------------------*/
static int Set_Count
(
   bits set
)
{
   int n, c = 0;

   for (n = 0; n < 32; n++)
      if ((set & 1 << n) != 0)
         c++;

   return c;
}
/*-----------------------------------------------------------------------*/

static osbool Set_Elem
(
   int  n,
   bits set
)
{
   return (set & 1 << n) != NONE;
}
/*-----------------------------------------------------------------------*/

/*Is type |t| 1 byte wide?*/

static osbool Byte_Wide
(
   def_t t,
   lookup_t types
)
{
   def_t t1;

   switch (t->tag)
   {
      case def_TYPE_CHAR: case def_TYPE_BYTE:
         return TRUE;
      break;

      case def_TYPE_ID:
         if (lookup (types, t->data AS id, (void **) &t1) == NULL)
            /*It's a locally defined type - use its definition.*/
            return Byte_Wide (t1, types);
         else if (lookup (main_byte_wide, t->data AS id, NULL) == NULL)
            /*It's in the byte-wide table.*/
            return TRUE;
         else
         {
            /*Give a warning and assume it's a word.*/
            if (!Quiet)
               fprintf (stderr, "warning: type \"%s\" not defined in this "
                  "module - assuming it's a word\n", t->data AS id);
            return FALSE;
         }
      break;

      default:
         return FALSE;
      break;
   }
}
/*-----------------------------------------------------------------------*/

static int Emit_Header
(
   FILE *file,
   char *title,
   char *author
)
{
   time_t now;

   return
      fprintf
      (
         file,
         ";ObjAsm file for %s\n"
         ";written by DefMod (%s) on %s"
         ";%s\n"
         "\n"
         "R0      RN      0\n"
         "R1      RN      1\n"
         "R2      RN      2\n"
         "R3      RN      3\n"
         "R4      RN      4\n"
         "R5      RN      5\n"
         "R6      RN      6\n"
         "R7      RN      7\n"
         "R8      RN      8\n"
         "R9      RN      9\n"
         "\n"
         "A1      RN      0\n"
         "A2      RN      1\n"
         "A3      RN      2\n"
         "A4      RN      3\n"
         "V1      RN      4\n"
         "V2      RN      5\n"
         "V3      RN      6\n"
         "V4      RN      7\n"
         "V5      RN      8\n"
         "V6      RN      9\n"
         "\n"
         "R       RN      0\n"
         "\n"
         "SL      RN      10\n"
         "FP      RN      11\n"
         "IP      RN      12\n"
         "SP      RN      13\n"
         "LR      RN      14\n"
         "PC      RN      15\n"
         "\n",
         title,
         /*__DATE__*/ Module_Date,
         (time (&now), ctime (&now)),
         author
      );
}
/*-----------------------------------------------------------------------*/

static int Emit0
(
   FILE *file,
   char *s0
)
{
   int rc = 0;

   rc = fprintf (file, "%-7s %s\n", Local != NULL? Local: "", s0);
   Local = NULL;
   return rc;
}
/*-----------------------------------------------------------------------*/

static int Emit1
(
   FILE *file,
   char *s0,
   char *s1
)
{
   int rc = 0;

   rc = fprintf (file, "%-7s %-7s %s\n",
         Local != NULL? Local: "", s0, s1);
   Local = NULL;
   return rc;
}
/*-----------------------------------------------------------------------*/

static int Emit2
(
   FILE *file,
   char *s0,
   char *s1,
   char *s2
)
{
   int rc = 0;

   rc = fprintf (file, "%-7s %-7s %s, %s\n",
         Local != NULL? Local: "", s0, s1, s2);
   Local = NULL;
   return rc;
}
/*-----------------------------------------------------------------------*/

static int Emit3
(
   FILE *file,
   char *s0,
   char *s1,
   char *s2,
   char *s3
)
{
   int rc = 0;

   rc = fprintf (file, "%-7s %-7s %s, %s, %s\n",
         Local != NULL? Local: "", s0, s1, s2, s3);
   Local = NULL;
   return rc;
}
/*-----------------------------------------------------------------------*/

static int Emit_Label
(
   FILE *file,
   char *s0
)
{
   return fprintf (file, "%s ROUT\n", s0);
}
/*-----------------------------------------------------------------------*/

static int Emit_Local
(
   FILE *file,
   char *s0
)
{
   file = file;

   Local = s0;
   return 0;
}
/*-----------------------------------------------------------------------*/

static int Emit_Line
(
   FILE *file
)
{
   return fprintf (file, "\n");
}
/*-----------------------------------------------------------------------*/

static int Emit_Comment
(
   FILE *file,
   char *comment
)
{
   return fprintf (file, ";%s\n", comment);
}
/*-----------------------------------------------------------------------*/

static int Format_APCS_List
(
   char *s0,
   bits  set,
   osbool hat
)
{
   char *cc = s0;
   int n, rc = 0;

   for (n = 0; n < 16; n++)
      if (Set_Elem (n, set))
      {
         if ((rc = sprintf (cc, cc == s0? "{%s": ", %s", APCS_Names [n]))
               < 0)
            return rc;

         cc += strlen (cc);
      }

   return sprintf (cc, cc == s0? "{}%s": "}%s", hat? "^": "");
}
/*-----------------------------------------------------------------------*/

static int Format_SWI_List
(
   char *s0,
   bits  set,
   osbool hat
)
{
   char *cc = s0;
   int n, rc = 0;

   for (n = 0; n < 16; n++)
      if (Set_Elem (n, set))
      {
         if ((rc = sprintf (cc, cc == s0? "{%s": ", %s", SWI_Names [n]))
               < 0)
            return rc;

         cc += strlen (cc);
      }

   return sprintf (cc, cc == s0? "{}%s": "}%s", hat? "^": "");
}
/*-----------------------------------------------------------------------*/

static int Emit_Code
(
   FILE    *file,
   char    *swi,
   def_s    s,
   osbool   nonx,
   osbool   apcs32,
   lookup_t types
)
{
   char c_name [def_ID_LIMIT + 1],
              c_plus_plus_name [def_C_PLUS_PLUS_LIMIT + 1], s1 [127 + 1],
              s2 [127 + 1];
   bits o, v, a, p, w; /*sets*/
   int  ni, no, nc, nk, nv, na, np, nw, /*set counts*/
              n, m, /*menials*/
              f, rc = 0;
   enum {LR_IN_IP, LR_STACKED, LR_PRESERVED} mode;
   osbool       use_block;

   Local = NULL;

   def_as_extern (c_name + (!nonx? 1: 0), swi);
#if 0
   def_as_c_plus_plus (c_plus_plus_name + (!nonx? 1: 0), swi, s);
#endif
   if (!nonx) c_name [0] = c_plus_plus_name [0] = 'x';

   /*Count the various set sizes*/
   ni = Set_Count (s->i);
   nc = Set_Count (s->c);
   nk = Set_Count (s->k);

   /*We handle the case of result registers by

         (a) deleting the result bit from the output argument set (s->o,
      s->f), so that the calculations of numbers of arguments work;
         (b) emitting a 'MOV R, result' at the end.
   */
   o = s->o, f = s->f_out;
   if (nonx)
   {
      if (!Set_Elem (def_FLAGS, s->value))
         o &= ~s->value;
      else
         f = 0;
   }
   no = Set_Count (o);

   /*The set of variable registers that must be saved by the function.*/
   v = (s->i | o | s->c | s->k | s->value) & ~(1 << 15 | 0xF);
      /*need never save R0, R1, R2, R3 or PC*/
   nv = Set_Count (v);

   /*The set of argument registers that are going to be needed as addresses
      at which to store results, and the set of such registers that are
      preserved by the SWI.*/
   a = NONE;
   p = NONE;
   for (n = 0; n < 4; n++)
      if (ni <= n && n < ni + no + f)
      {
         /*This is an output argument.*/
         if (Set_Elem (n, s->i | o | s->c | s->k | s->value))
            /*This is corrupted by the SWI (or the veneer).*/
            a |= 1 << n;
         else
            /*This is preserved.*/
            p |= 1 << n;
      }
   na = Set_Count (a);
   np = Set_Count (p);

   mode = ni > 4? LR_STACKED: nonx? LR_PRESERVED: LR_IN_IP;
      /*assume that the non-x form can only be called in user mode*/

   use_block = def_using_block (s);

   if ((rc = Emit_Label (file, c_name)) < 0)
      goto finish;
#if 0
   if ((rc = Emit_Label (file, c_plus_plus_name)) < 0)
      goto finish;
#endif

   if (mode == LR_STACKED)
   {
      if ((rc = Emit2 (file, "MOV", "IP", "SP")) < 0)
         goto finish;
   }
   else if (mode == LR_IN_IP)
   {
      if ((rc = Emit2 (file, "MOV", "IP", "LR")) < 0)
         goto finish;
   }

   if (!use_block)
   {
      /*The set of registers that we can use as workspace because: they
         are not needed on input; they are not set or corrupted on
         output; they need not be preserved by A P C S.*/
      w = 0xFu & ~s->i & ~o & ~s->c & ~s->k & ~s->value & ~p;
      nw = Set_Count (w);
            /*Be careful not to use them until their contents are used!*/

      if (nv + na > 0)
      {
         if ((rc = Format_APCS_List (s1,
               v | a | (mode == LR_STACKED? 1 << 14: 0), FALSE)) < 0)
            goto finish;

         if ((rc = Emit2 (file, "STMFD", "SP!", s1)) < 0)
            goto finish;
      }

      if (ni > 4)
      {
         /*We have to make a set of all but the first 4 input arguments
            (which are in registers already)*/
         bits set = NONE;
         int count = 0;

         for (n = 0; n < 16; n++)
            if (Set_Elem (n, s->i) && count++ >= 4)
               set |= 1 << n;

         if ((rc = Format_SWI_List (s1, set, FALSE)) < 0)
            goto finish;
         if ((rc = Emit2 (file, "LDMFD", "IP", s1)) < 0)
            goto finish;
      }

      for (m = 3; m >= 0; m--)
         if (ni > m)
            if ((n = def_bit_index (s->i, m)) != m)
               if ((rc = Emit2 (file, "MOV", SWI_Names [n],
                     APCS_Names [m])) < 0)
                  goto finish;
   }
   else
   {
      /*Much easier - save all the arguments and load the (unique)
         input register with the location they've been saved to. Only
         then can you save any variables.*/

      /*The set of registers that we can use as workspace because: they
         are not needed on input; they are not set or corrupted on
         output; they need not be preserved by A P C S.*/
      w = NONE;
      nw = 0;

      if ((rc = Emit2 (file, "STMFD", "SP!", "{A1, A2, A3, A4}")) < 0)
         goto finish;
         /*SP is NOW pointing at the block to be passed to the SWI*/

      if (nv > 0)
      {
         if ((rc = Format_APCS_List (s1, v, FALSE)) < 0)
            goto finish;

         if ((rc = Emit2 (file, "STMFD", "SP!", s1)) < 0)
            goto finish;
      }

      if ((rc = sprintf (s1, "#%d", 4*nv)) < 0)
         goto finish;

      for (n = 0; n < 10; n++)
         if (Set_Elem (n, s->i))
            if ((rc = Emit3 (file, "ADD", SWI_Names [n], "SP", s1)) < 0)
               goto finish;
      /*we know that LR is in IP*/
   }

   if (nk > 0)
      for (n = 9; n >= 0; n--)
         if (Set_Elem (n, s->k))
         {
            if (Set_Elem (n, s->i))
            {
               int  j, low;
               bits k = s->constants [n];

                  /*If there is an input register, OP the constant with it
                     instead of moving it in, where OP is the operation in
                     the SWI definition. This fails badly with AND, so it's
                     not supported.*/
               low = 0;
               while (TRUE)
               {
                  /*Find the bottom 1 bit of |k| not yet emitted.*/
                  m = 0;
                  for (j = low; j < 32; j += 2)
                     if ((k & 3 << j) != NONE)
                     {
                        m = k & 0xFF << j;
                        low = j + 8;
                        break;
                     }

                  if (m == 0) break; /*no more bits*/

                  if ((rc = sprintf (s2, "#&%X", m)) < 0)
                     goto finish;

                  if ((rc = Emit3 (file, Op (s->op [n]), SWI_Names [n],
                        SWI_Names [n], s2)) < 0)
                     goto finish;
               }
            }
            else
            {
               int  j, low;
               bits k = s->constants [n];
               osbool first = TRUE, negative = Set_Count (k) > 16;

               if (negative) k = ~k;
               low = 0;
               while (TRUE)
               {
                  /*Find the bottom 1 bit of |k| not yet emitted.*/
                  m = 0;
                  for (j = low; j < 32; j += 2)
                     if ((k & 3 << j) != NONE)
                     {
                        m = k & 0xFF << j;
                        low = j + 8;
                        break;
                     }

                  if (m == 0) break; /*no more bits*/

                  if ((rc = sprintf (s2, "#&%X", m)) < 0)
                     goto finish;

                  if (first)
                  {
                     if ((rc = Emit2 (file, !negative? "MOV": "MVN",
                           SWI_Names [n], s2)) < 0)
                        goto finish;
                     first = FALSE;
                  }
                  else
                  {
                     if ((rc = Emit3 (file, !negative? "ORR": "BIC",
                           SWI_Names [n], SWI_Names [n], s2)) < 0)
                        goto finish;
                  }
               }

               if (first)
                  /*If we escaped with no effect yet, the register must be
                     cleared.*/
                  if ((rc = Emit2 (file, !negative? "MOV": "MVN",
                        SWI_Names [n], "#0")) < 0)
                     goto finish;
            }
         }

#if 0
   /*Experimental!*/
   for (n = 0; n < 4; n++)
      if (Set_Elem (n, w))
         /*Trash register |n| now, just to show we can do it.*/
         if ((rc = Emit2 (file, "MOV", SWI_Names [n], "#&FF0")) < 0)
            goto finish;
#endif

   /*Registers now set up properly - do the business part.*/
   if ((rc = sprintf (s1, "&%X", s->swi | (!nonx? 1 << 17: 0))) < 0)
      goto finish;

   if ((rc = Emit1 (file, "SWI", s1)) < 0)
      goto finish;

#if 0
   /*Experimental!*/
   for (n = 0; n < 4; n++)
      if (Set_Elem (n, w))
      {  /*Check that register |n| still has its contents.*/
         if ((rc = Emit2 (file, "TEQ", SWI_Names [n], "#&FF0")) < 0)
            goto finish;
         if ((rc = Emit2 (file, "MOVNE", "PC", "#0")) < 0)
            goto finish;
      }
#endif

   /*All over bar the shouting ...*/
   if (no + f > 0)
   {
      if (!nonx)
         if ((rc = Emit1 (file, "BVS", "%99")) < 0)
            goto finish;

      for (n = 0; n < no + f; n++)
      {
         int reg;
         osbool store_byte;

         /*Do we want a STRNE or a STRNEB?*/
         if (n != no)
         {
            reg = def_bit_index (o, n);
            store_byte = !Set_Elem (reg, s->ro) &&
                  Byte_Wide (s->outputs [reg], types);
         }
         else
         {
            if (apcs32)
            {
               reg = (nonx && !Set_Elem (0, s->value)) ? 0 : 1;

               if ((rc = Emit3 (file, "RSB", SWI_Names [reg], "PC", "PC")) < 0)
                  goto finish;
               if ((rc = Emit2 (file, "MRS", SWI_Names [reg], "CPSR")) < 0)
                  goto finish;
            }
            else
            {
               reg = 15; /*PC*/
            }

            store_byte = FALSE;
         }

         /*Output argument |n| is in parameter position |ni + n|. Is this
            safe?*/
         if (Set_Elem (ni + n, p))
         {
            /*The address is still where it was on entry, undisturbed.*/
            if ((rc = Emit2 (file, "TEQ", APCS_Names [ni + n], "#0")) < 0)
               goto finish;

            if ((rc = sprintf (s1, "[%s]", APCS_Names [ni + n])) < 0)
               goto finish;
            if ((rc = Emit2 (file, !store_byte? "STRNE": "STRNEB",
                  SWI_Names [reg], s1)) < 0)
               goto finish;
         }
         else
         {
            /*The address has been stacked.*/
            int offset, i;
            char *tmp = mode == LR_PRESERVED? "IP": "LR";

            /*If the argument number that this argument has is in the first
               4, then it has been stacked above. Otherwise, it has been
               stacked by A P C S, and we have stacked |nv| variables and
               |na| other addresses (and maybe LR) above it.*/
            if (ni + n < 4)
            {
               offset = 0;
               for (i = 0; i < n; i++)
                  if (Set_Elem (ni + i, a))
                     offset += 4;
            }
            else
               offset = 4*(ni + n - 4 +
                     nv + na + (mode == LR_STACKED? 1: 0));

            if ((rc = sprintf (s1, "[SP, #%d]", offset)) < 0)
               goto finish;
            if ((rc = Emit2 (file, "LDR", tmp, s1)) < 0)
               goto finish;

            if ((rc = Emit2 (file, "TEQ", tmp, "#0")) < 0)
               goto finish;

            if ((rc = sprintf (s1, "[%s]", tmp)) < 0)
               goto finish;
            if ((rc = Emit2 (file, !store_byte? "STRNE": "STRNEB",
                  SWI_Names [reg], s1)) < 0)
               goto finish;
         }
      }

      if (!nonx)
      {
         if ((rc = Emit2 (file, "MOV", "R", "#0")) < 0)
            goto finish;
         if ((rc = Emit_Local (file, "99")) < 0)
            goto finish;
      }
      else
      {
         /*Value to return?*/
         if (!(s->value == NONE || Set_Elem (0, s->value)))
         {
            /*If no value is wanted, or if R0 is required, leave R alone.*/
            if (apcs32 && Set_Elem(def_FLAGS, s->value))
            {
               if ((rc = Emit3 (file, "RSB", "R", "PC", "PC")) < 0)
                  goto finish;
               if ((rc = Emit2 (file, "MRS", "R", "CPSR")) < 0)
                  goto finish;
            }
            else
            {
               if ((rc = Emit2 (file, "MOV", "R",
                     SWI_Names [def_bit_index (s->value, 0)])) < 0)
                  goto finish;
            }
         }
      }
   }
   else
   {
      /*No output registers to worry about.*/
      if (!nonx)
      {
         if ((rc = Emit2 (file, "MOVVC", "R", "#0")) < 0)
            goto finish;
      }
      else
      {
         /*Value to return?*/
         if (!(s->value == NONE || Set_Elem (0, s->value)))
         {
            /*If no value is wanted, or if R0 is required, leave R alone.*/
            if (apcs32 && Set_Elem(def_FLAGS, s->value))
            {
               if ((rc = Emit3 (file, "RSB", "R", "PC", "PC")) < 0)
                  goto finish;
               if ((rc = Emit2 (file, "MRS", "R", "CPSR")) < 0)
                  goto finish;
            }
            else
            {
               if ((rc = Emit2 (file, "MOV", "R",
                     SWI_Names [def_bit_index (s->value, 0)])) < 0)
                  goto finish;
            }
         }
      }
   }

   /*The tidy up code*/
   if (!use_block)
   {
      if (na > 0)
      {
         if ((rc = sprintf (s1, "#%d", 4*na)) < 0)
            goto finish;
         if ((rc = Emit3 (file, "ADD", "SP", "SP", s1)) < 0)
            goto finish;
      }

      if (nv > 0)
      {
         if ((rc = Format_APCS_List (s1,
               v | (mode == LR_STACKED? 1 << 15: 0),
               /*hat?*/ mode == LR_STACKED && !apcs32)) < 0)
            goto finish;
         if ((rc = Emit2 (file, "LDMFD", "SP!", s1)) < 0)
            goto finish;
      }
   }
   else
   {
      /*Done in a different order in this case. First restore variables,
         if any.*/
      if (nv > 0)
      {
         if ((rc = Format_APCS_List (s1, v, FALSE)) < 0)
            goto finish;

         if ((rc = Emit2 (file, "LDMFD", "SP!", s1)) < 0)
            goto finish;
      }

      /*Then skip over the saved four arguments.*/
      if ((rc = Emit3 (file, "ADD", "SP", "SP", "#16")) < 0)
         goto finish;
   }

   if (mode == LR_IN_IP)
   {
      if ((rc = Emit2 (file, apcs32? "MOV": "MOVS", "PC", "IP")) < 0)
         goto finish;
   }
   else if (mode == LR_PRESERVED)
   {
      if ((rc = Emit2 (file, apcs32? "MOV": "MOVS", "PC", "LR")) < 0)
         goto finish;
   }

   if (nw > 0)
   {
      if ((rc = Format_SWI_List (s1, w, FALSE)) < 0)
         goto finish;
      sprintf (s2, "Registers available for scratch use: %s", s1);
      if ((rc = Emit_Comment (file, s2)) < 0)
         goto finish;
   }

   if ((rc = Emit_Line (file)) < 0)
      goto finish;

finish:
   return rc;
}
/*-----------------------------------------------------------------------*/

os_error *objasm_output
(
   FILE     *file,
   char     *title,
   char     *author,
   lookup_t  needs,
   lookup_t  consts,
   lookup_t  types,
   lookup_t  swis,
   osbool    separate, /*if |separate|, output is to the directory |output|; otherwise,
                          to the file |file|.*/
   osbool    apcs32,
   char     *output
)
{
   os_error *error = NULL;
   char     *swi;
   def_s     s;
   void     *context;
   int       rc = 0;
   osbool    done_fopen_via_file = FALSE, done_fopen_object = FALSE;
   FILE     *via_file = NULL, *object = NULL /*for Norcroft*/;

   needs = needs, consts = consts, types = types;

#ifdef EXECUTE_ON_UNIX
   char *asmcmd = getenv("ASMCMD");
   if (asmcmd == NULL)
      asmcmd = ASMCMD;
#endif

   if (!separate)
   {
      if ((rc = Emit_Header (file, title, author)) < 0)
         goto finish;

      /*Emit all EXPORT statements.*/
      context = 0;
      while (TRUE)
      {
         char c_name [def_ID_LIMIT + 1],
                    c_plus_plus_name [def_C_PLUS_PLUS_LIMIT + 1];

         if ((error = lookup_enumerate (swis, &swi, (void **) &s,
               &context)) != NULL)
            goto finish;
         if (context == 0) break;

         if (!s->absent)
         {
            /*Export the symbol with a preceding 'x.'*/
            def_as_extern (c_name + 1, swi);
            c_name [0] = 'x';
            #if 0
            def_as_c_plus_plus (c_plus_plus_name + 1, swi, s);
            #endif
            c_plus_plus_name [0] = 'x';

            if ((rc = Emit1 (file, "EXPORT", c_name)) < 0)
               goto finish;
            if ((rc = Emit1 (file, "EXPORT", c_name + 1)) < 0)
               goto finish;
#if 0
            if ((rc = Emit1 (file, "EXPORT", c_plus_plus_name)) < 0)
               goto finish;
            if ((rc = Emit1 (file, "EXPORT", c_plus_plus_name + 1)) < 0)
               goto finish;
#endif
         }
      }
      if ((rc = Emit_Line (file)) < 0)
         goto finish;

      if ((rc = Emit2 (file, "AREA", "|SWI$$Code|", "CODE, READONLY, PIC")) < 0)
         goto finish;
      if ((rc = Emit_Line (file)) < 0)
         goto finish;

      /*Emit code.*/
      context = 0;
      while (TRUE)
      {
         if ((error = lookup_enumerate (swis, &swi, (void **) &s,
               &context)) != NULL)
            goto finish;

         if (context == 0) break;

         if (!s->absent)
         {
            if ((rc = Emit_Code (file, swi, s, FALSE, apcs32, types)) < 0)
               goto finish;
            if ((rc = Emit_Code (file, swi, s, TRUE, apcs32, types)) < 0)
               goto finish;
         }

         if (def_inlinable (s) && !Quiet)
            if ((rc = fprintf (stderr, "information: SWI %s could be "
                  "inlined\n", swi)) < 0)
               goto finish;
      }

      if ((rc = Emit0 (file, "END")) < 0)
         goto finish;
   }
   else
   {
      char name1 [FILENAME_MAX + 1], name2 [FILENAME_MAX + 1],
                 cmd [1024];
      int  count = 0;

      /*Ensure the directory exists.*/
      if ((error = xosfile_create_dir (output, 0)) != NULL)
         goto finish;

#if 0
      /*Get the prefix - needed because we'll be starting a subtask.*/
      if ((error = xos_read_var_val ("Prefix$Dir", prefix_dir,
            FILENAME_MAX, context1, os_VARTYPE_EXPANDED, &len, &context1,
            &var_type)) != NULL)
         goto finish;

      if (len == 0)
         strcpy (prefix_dir, "@");
      else
         prefix_dir [len] = '\0';
#endif

#ifndef EXECUTE_ON_UNIX
      if ((via_file = fopen ("ViaFile", "w+")) == NULL)
      {
         error = (os_error*)_kernel_last_oserror ();
         goto finish;
      }
      done_fopen_via_file = TRUE;
#endif

      context = 0;
      while (TRUE)
      {
         char c_name [def_ID_LIMIT + 1],
                    c_plus_plus_name [def_C_PLUS_PLUS_LIMIT + 1],
                    dir_name [FILENAME_MAX + 1];
         osbool nonx;

         if ((error = lookup_enumerate (swis, &swi, (void **) &s,
               &context)) != NULL)
            goto finish;
         if (context == 0) break;

         if (!s->absent)
         {
            for (nonx = FALSE; nonx <= TRUE; nonx++)
            {
               def_as_extern (c_name + (!nonx? 1: 0), swi);
#if 0
               def_as_c_plus_plus (c_plus_plus_name +
                     (!nonx? 1: 0), swi, s);
#endif
               if (!nonx) c_name [0] = c_plus_plus_name [0] = 'x';

               /*Get the SWI number truncated to 4 bits.*/
               if ((rc = sprintf (dir_name, "0x%.5X%s", s->swi & 0xDFFF0,
                     !nonx? "x": "")) < 0)
                  goto finish;
#ifdef EXECUTE_ON_UNIX
               if ((rc = sprintf (name1, "%s/%s.%.3d.s", output, dir_name,
                     count)) < 0)
                  goto finish;
               if ((rc = sprintf (name2, "%s/%s.%.3d.o", output, dir_name,
                     count)) < 0)
                  goto finish;
#else
               if ((rc = sprintf (name1, "%s.%s", output, dir_name)) < 0)
                  goto finish;
               if ((error = xosfile_create_dir (name1, 0)) != NULL)
                  goto finish;

               if ((rc = sprintf (name1, "%s.%s.s", output, dir_name)) < 0)
                  goto finish;
               if ((error = xosfile_create_dir (name1, 0)) != NULL)
                  goto finish;
               if ((rc = sprintf (name2, "%s.%s.o", output, dir_name)) < 0)
                  goto finish;
               if ((error = xosfile_create_dir (name2, 0)) != NULL)
                  goto finish;

               if ((rc = sprintf (name1, "%s.%s.s.%.3d", output, dir_name,
                     count)) < 0)
                  goto finish;
               if ((rc = sprintf (name2, "%s.%s.o.%.3d", output, dir_name,
                     count)) < 0)
                  goto finish;
#endif
               if ((object = fopen (name1, "w")) == NULL)
               {
                  error = (os_error*)_kernel_last_oserror ();
                  goto finish;
               }
               done_fopen_object = TRUE;

               if ((rc = Emit_Header (object, swi, author)) < 0)
                  goto finish;

               if ((rc = Emit1 (object, "EXPORT", c_name)) < 0)
                  goto finish;
#if 0
               if ((rc = Emit1 (object, "EXPORT", c_plus_plus_name)) < 0)
                  goto finish;
#endif

               if ((rc = Emit_Line (object)) < 0)
                  goto finish;

               if ((rc = Emit2 (object, "AREA",
                     "|SWI$$Code|", "CODE, READONLY, PIC")) < 0)
                  goto finish;
               if ((rc = Emit_Line (object)) < 0)
                  goto finish;

               if ((rc = Emit_Code (object, swi, s, nonx, apcs32, types)) < 0)
                  goto finish;

               if ((rc = Emit0 (object, "END")) < 0)
                  goto finish;

               if (fclose (object) == EOF)
               {
                  error = (os_error*)_kernel_last_oserror ();
                  goto finish;
               }
               done_fopen_object = FALSE;

#if 0
               if ((rc = sprintf (cmd, "ObjAsm -from %s.%s -to %s.%s "
                     "-stamp -quit -apcs 3/%dbit/SWstackcheck", prefix_dir,
                     name1, prefix_dir, name2, apcs32 ? 32 : 26))
                     < 0)
                  goto finish;
#else
#ifdef EXECUTE_ON_UNIX
               if ((rc = sprintf (cmd, "%s -from %s -to %s "
                     "-stamp -quit -apcs 3/%dbit/SWstackcheck",
                     asmcmd, name1, name2, apcs32 ? 32 : 26)) < 0)
#else
               if ((rc = sprintf (cmd, ASMCMD " -from %s -to %s "
                     "-stamp -quit -apcs 3/%dbit/SWstackcheck",
                     name1, name2, apcs32 ? 32 : 26)) < 0)
#endif
                  goto finish;
#endif

               tracef ("%s ...\n" _ cmd);
               if (!Quiet)
                  if ((rc = printf ("Assembling %s\n", name1)) < 0)
                     goto finish;

               if (system (cmd) != 0)
               {
                  error = (os_error*)_kernel_last_oserror ();
                  if( error == 0 )
                  {
                     x_LOCAL_ERROR(e, 0, "Non-zero return code");
                     error = (os_error *) e;
                  }
                  goto finish;
               }
               tracef ("%s ... done\n" _ cmd);

               if (done_fopen_via_file)
                  if ((rc = fprintf (via_file, "%s\n", name2)) < 0)
                     goto finish;
            }
         }

         count++;
      }
   }

finish:
   if (rc < 0) error = (os_error*)_kernel_last_oserror ();

   if (done_fopen_object && fclose (object) == EOF && error == NULL)
      error = (os_error*)_kernel_last_oserror ();

   if (done_fopen_via_file && fclose (via_file) == EOF && error == NULL)
      error = (os_error*)_kernel_last_oserror ();

   return error;
}