m3linker/src/MxGen.m3


 Copyright 1996-2000, Critical Mass, Inc.  All rights reserved. 
 See file COPYRIGHT-CMASS for details. 

MODULE MxGen;

IMPORT Fmt, Wr, Thread, Stdio;
IMPORT Mx, MxRep, MxMap, M3ID, Target;
IMPORT M3CG, M3CG_Ops, TInt, TargetMap;

CONST (* name-mangling done by the compiler & back-end *)
  BinderSuffix = ARRAY BOOLEAN OF TEXT { "_M3", "_I3" };

TYPE
  State = RECORD
    base          : Mx.LinkSet   := NIL;
    errors        : Wr.T         := NIL;
    verbose       : BOOLEAN      := FALSE;
    gui           : BOOLEAN      := FALSE;
    genC          : BOOLEAN      := FALSE;
    lazyInit      : BOOLEAN      := TRUE;
    main_units    : UnitInfo     := NIL;
    all_units     : UnitInfo     := NIL;
    top_units     : UnitInfo     := NIL;
    used_units    : UnitInfo     := NIL;
    imported_units: UnitInfo     := NIL;

    (* C output information *)
    wr            : Wr.T         := NIL;
    eol           : TEXT         := NIL;

    (* M3CG output information *)
    cg            : M3CG.T       := NIL;
  END;

TYPE
  UnitInfo = REF RECORD
    next    : UnitInfo;
    unit    : Mx.Unit;
    cg_proc : M3CG.Proc := NIL;
    binder  : TEXT;
  END;

PROCEDURE ContainsUnit (ui: UnitInfo; u: Mx.Unit): BOOLEAN =
  BEGIN
    WHILE ui # NIL DO
      IF ui.unit = u THEN RETURN TRUE END;
      ui := ui.next;
    END;
    RETURN FALSE;
  END ContainsUnit;
------------------------------------------------------------------------

PROCEDURE GenerateMain (base: Mx.LinkSet;  c_output: Wr.T;  cg_output: M3CG.T;
                        verbose: BOOLEAN;  windowsGUI: BOOLEAN;
                        lazy := FALSE) =
  VAR s: State;
  BEGIN
    <*ASSERT  (c_output = NIL) # (cg_output = NIL) *>
    s.base      := base;
    s.wr        := c_output;
    s.cg        := cg_output;
    s.verbose   := verbose;
    s.errors    := Stdio.stdout;
    s.gui       := windowsGUI;
    s.genC      := (s.wr # NIL);
    s.eol       := Target.EOL;
    s.lazyInit  := lazy;

    IF verbose THEN
      INC(debugLevel);
    END;
    IF s.genC
      THEN GenCTypeDecls (s);
      ELSE GenCGTypeDecls (s);
    END;
    IF lazy THEN
      ImportMain (s);
    ELSE
      ImportTopUnits (s);
    END;
    IF s.genC
      THEN GenerateCEntry (s);
      ELSE GenerateCGEntry (s);
    END;
  END GenerateMain;
------------------------------------------------------------------------

PROCEDURE GenCTypeDecls (<*UNUSED*> VAR s: State) =
  BEGIN
  END GenCTypeDecls;

PROCEDURE GenCGTypeDecls (VAR s: State) =
  VAR
    interface    : M3CG.Var;
    struct_align := MAX (Target.Structure_size_boundary, Target.Address.align)
                      DIV Target.Byte; (* == min structure alignment (bytes)*)
  BEGIN
    s.cg.begin_unit ();
    s.cg.set_source_file ("_m3main.mc");
    s.cg.set_source_line (1);

    (* we only need to declare a global segment so the gcc-based backend
       can pick up the unit name, "_m3main".   But then, the native x86
       backend requires that it be explicitly initialized... *)
    interface := s.cg.declare_segment (M3ID.Add ("MM__m3main"), 0, FALSE);
    s.cg.bind_segment (interface, Target.Address.bytes, struct_align,
                       Target.CGType.Struct, FALSE, TRUE);
    s.cg.begin_init (interface);
    s.cg.end_init (interface);
  END GenCGTypeDecls;
------------------------------------------------------------------------

PROCEDURE ImportMain (VAR s: State) =
  VAR
    main  := M3ID.Add ("Main");
    units := MxMap.GetData (s.base.modules);
    u     : Mx.Unit;
  BEGIN
    s.main_units := NIL;

    (* find the modules exporting "Main" *)
    FOR i := 0 TO LAST (units^) DO
      u := units[i].value;
      IF (u # NIL) THEN
        FOR i := u.exported_units.start
              TO u.exported_units.start + u.exported_units.cnt - 1 DO
          IF (u.info[i] = main) THEN
            s.main_units := NEW (UnitInfo, unit := u, next := s.main_units);
            ImportUnit (s, s.main_units);
            EXIT;
          END;
        END;
      END;
    END;

    IF s.main_units = NIL THEN
      Err (s, "No module implements \"Main\".", s.eol);
    END;
  END ImportMain;

PROCEDURE ImportTopUnits (VAR s: State) =
  VAR
    main  := M3ID.Add ("Main");
    mods  := s.base.modules;
    units := MxMap.GetData (mods);
    u, v  : Mx.Unit;
    ui    : UnitInfo;
    found : BOOLEAN;
  BEGIN
    s.main_units := NIL;

    FOR i := 0 TO LAST (units^) DO
      u := units[i].value;
      IF (u # NIL) THEN
        Debug (2, UnitName (u));
        FOR i := u.exported_units.start
              TO u.exported_units.start + u.exported_units.cnt - 1 DO
          IF (u.info[i] = main) THEN
            s.main_units := NEW (UnitInfo, unit := u, next := s.main_units);
            s.main_units.binder := UnitName (s.main_units.unit);
            EXIT;
          END;
        END;
        IF u.imported_units.cnt > 0 THEN
          found := FALSE;
          FOR i := u.imported_units.start
            TO u.imported_units.start + u.imported_units.cnt - 1 DO
            v := MxMap.Get (mods, u.info[i]);
            IF v # NIL AND v # u THEN
              Debug (2, "  imports ", UnitName (v));
              found := TRUE;
              IF NOT ContainsUnit (s.imported_units, v) THEN
                s.imported_units :=
                    NEW (UnitInfo, unit := v, next := s.imported_units);
              END;
            END;
          END;
        END;
        IF u.used_modules.cnt > 0 THEN
          found := FALSE;
          FOR i := u.used_modules.start
            TO u.used_modules.start + u.used_modules.cnt - 1 DO
            v := MxMap.Get (mods, u.info[i]);
            IF v # NIL AND v # u THEN
              Debug (2, "  uses ", UnitName (v));
              found := TRUE;
              IF NOT ContainsUnit (s.used_units, v) THEN
                s.used_units :=
                    NEW (UnitInfo, unit := v, next := s.used_units);
              END;
            END;
          END;
        END;
        s.all_units := NEW (UnitInfo, unit := u, next := s.all_units);
      END;
    END;

    FOR i := 0 TO LAST (units^) DO
      u := units[i].value;
      IF (u # NIL) THEN
        Debug (3, "checking unit ", UnitName (u));
        IF NOT ContainsUnit (s.imported_units, u) AND
          NOT ContainsUnit (s.used_units, u) AND
          NOT ContainsUnit (s.main_units, u) THEN
          Debug (3, "  --> not used ==> top unit");
          s.top_units := NEW (UnitInfo, unit := u, next := s.top_units);
          s.top_units.binder := UnitName (s.top_units.unit);
        ELSE
          Debug (3, "  --> used");
        END;
      END;
    END;

    DumpUnits("Main Units:", s.main_units);
    DumpUnits("Imported Units:", s.imported_units);
    DumpUnits("Used Units:", s.used_units);
    DumpUnits("Top Units:", s.top_units);

    (* concatenate main and other top units *)
    (*
    ui := s.top_units;
    IF ui # NIL THEN
      WHILE ui.next # NIL DO
        ui := ui.next;
      END;
      ui.next := s.main_units;
      s.main_units := s.top_units;
    END;
    *)

    (* import all top units different from Main *)
    ui := s.top_units;
    WHILE ui # NIL DO
      ImportUnit (s, ui);
      ui := ui.next;
    END;

    (* import all main units *)
    ui := s.main_units;
    WHILE ui # NIL DO
      ImportUnit (s, ui);
      ui := ui.next;
    END;
    IF s.main_units = NIL THEN
      Err (s, "No module implements \"Main\".", s.eol);
    END;
  END ImportTopUnits;

PROCEDURE ImportUnit (VAR s: State;  ui: UnitInfo) =
  VAR u := ui.unit;
  BEGIN
    ui.binder := UnitName (u);
    IF s.genC THEN
      Out (s, "extern void* ", ui.binder, "();", s.eol);
    ELSE
      ui.cg_proc := s.cg.import_procedure (M3ID.Add (ui.binder), 1,
                                          Target.CGType.Addr,
                                          Target.DefaultCall);
      EVAL DeclareParam (s, "mode",  Target.Integer.cg_type);
    END;
  END ImportUnit;

PROCEDURE UnitName (u: Mx.Unit): TEXT =
  BEGIN
    IF u = NIL THEN RETURN "NIL" END;
    RETURN M3ID.ToText (u.name) & BinderSuffix [u.interface];
  END UnitName;

PROCEDURE DumpUnits (h: TEXT; units: UnitInfo) =
  VAR n := 1; nstr: TEXT;
  BEGIN
    IF debugLevel < 1 THEN RETURN END;
    Debug (1, h);
    WHILE (units # NIL) DO
      IF units.binder = NIL THEN
        units.binder := UnitName (units.unit);
      END;
      nstr := Fmt.F("%4s ", Fmt.Int(n));
      INC(n);
      Debug (1, nstr, units.binder);
      units := units.next;
    END;
    Debug (1);
  END DumpUnits;
------------------------------------------------------------------------

PROCEDURE GenerateCEntry (VAR s: State) =

  PROCEDURE GenAddUnits(ui: UnitInfo) =
    BEGIN
      WHILE (ui # NIL) DO
        Out (s, "  RTLinker__AddUnit (", ui.binder, ");", s.eol);
        ui := ui.next;
      END;
    END GenAddUnits;

  PROCEDURE GenAddUnitImports(ui: UnitInfo) =
    BEGIN
      IF s.lazyInit THEN RETURN END;
      WHILE (ui # NIL) DO
        Out (s, "  RTLinker__AddUnitImports (", ui.binder, ");", s.eol);
        ui := ui.next;
      END;
    END GenAddUnitImports;

  BEGIN
    Out (s, "extern void RTLinker__InitRuntime ();", s.eol);
    Out (s, "extern void RTLinker__AddUnit ();", s.eol);
    Out (s, "extern void RTLinker__AddUnitImports ();", s.eol);
    Out (s, "extern void RTProcess__Exit ();", s.eol, s.eol);

    IF (s.gui) THEN
      Out (s, "#include <windows.h>", s.eol);
      Out (s, "int WINAPI ");
      Out (s, "WinMain (HINSTANCE self, HINSTANCE prev,", s.eol);
      Out (s, "                    LPSTR args, int mode)", s.eol);
      Out (s, "{", s.eol);
      Out (s, "  RTLinker__InitRuntime (-1, args, ");
      Out (s,                        "GetEnvironmentStringsA(), self);", s.eol);
    ELSE
      Out (s, "int main (argc, argv, envp)", s.eol);
      Out (s, "int argc;", s.eol);
      Out (s, "char **argv;", s.eol);
      Out (s, "char **envp;", s.eol);
      Out (s, "{", s.eol);
      Out (s, "  RTLinker__InitRuntime (argc, argv, envp, 0);", s.eol);
    END;

    GenAddUnitImports(s.main_units);
    GenAddUnits(s.top_units);
    GenAddUnits(s.main_units);

    Out (s, "  RTProcess__Exit (0);", s.eol);
    Out (s, "  return 0;", s.eol);
    Out (s, "}", s.eol, s.eol);
  END GenerateCEntry;
------------------------------------------------------------------------

PROCEDURE GenerateCGEntry (VAR s: State) =
  VAR
    main      : M3CG.Proc;
    run_proc  : M3CG.Proc;
    link_proc : M3CG.Proc;
    link_proc2: M3CG.Proc;
    exit_proc : M3CG.Proc;
    getenv    : M3CG.Proc;
    winapi    : Target.CallingConvention;
    argv      : M3CG.Var;
    argc      : M3CG.Var;
    envp      : M3CG.Var;
    self      : M3CG.Var;
    prev      : M3CG.Var;
    mode      : M3CG.Var;
    src_line  : INTEGER := 2;
    int_t     := Target.Integer.cg_type;
    addr_t    := Target.CGType.Addr;

  PROCEDURE GenAddUnits(ui: UnitInfo) =
    BEGIN
      WHILE (ui # NIL) DO
        s.cg.set_source_line (src_line);  INC (src_line);
        s.cg.start_call_direct (link_proc, 0, Target.CGType.Void);
        s.cg.load_procedure (ui.cg_proc);
        s.cg.pop_param (addr_t);
        s.cg.call_direct (link_proc, Target.CGType.Void);
        ui := ui.next;
      END;
    END GenAddUnits;

  PROCEDURE GenAddUnitImports(ui: UnitInfo) =
    BEGIN
      IF s.lazyInit THEN RETURN END;
      WHILE (ui # NIL) DO
        s.cg.set_source_line (src_line);  INC (src_line);
        s.cg.start_call_direct (link_proc2, 0, Target.CGType.Void);
        s.cg.load_procedure (ui.cg_proc);
        s.cg.pop_param (addr_t);
        s.cg.call_direct (link_proc2, Target.CGType.Void);
        ui := ui.next;
      END;
    END GenAddUnitImports;

  BEGIN
    run_proc := s.cg.import_procedure (M3ID.Add ("RTLinker__InitRuntime"), 4,
                                       Target.CGType.Void, Target.DefaultCall);
    EVAL DeclareParam (s, "argc", int_t);
    EVAL DeclareParam (s, "argv", addr_t);
    EVAL DeclareParam (s, "envp", addr_t);
    EVAL DeclareParam (s, "instance", addr_t);

    link_proc := s.cg.import_procedure (M3ID.Add ("RTLinker__AddUnit"), 1,
                                       Target.CGType.Void, Target.DefaultCall);
    EVAL DeclareParam (s, "m", addr_t);

    link_proc2 := s.cg.import_procedure (M3ID.Add ("RTLinker__AddUnitImports"),
                                         1, Target.CGType.Void,
                                         Target.DefaultCall);
    EVAL DeclareParam (s, "m", addr_t);

    exit_proc := s.cg.import_procedure (M3ID.Add ("RTProcess__Exit"), 1,
                                       Target.CGType.Void, Target.DefaultCall);
    EVAL DeclareParam (s, "n", int_t);

    IF (s.gui) THEN
      (*
         #include <windows.h>
         extern LPSTR WINAPI GetEnvironmentStringsA ();
      *)
      winapi := Target.FindConvention ("WINAPI");
      getenv := s.cg.import_procedure (M3ID.Add ("GetEnvironmentStringsA"), 0,
                                       addr_t, winapi);

      (* int WINAPI WinMain(HINSTANCE self, HINSTANCE prev, LPSTR args, int mode) *)
      main := s.cg.declare_procedure (M3ID.Add ("WinMain"), (*n_params*) 4,
                                 int_t, (*lev*) 0, winapi, TRUE, NIL);
      self := DeclareParam (s, "self", addr_t);
      prev := DeclareParam (s, "prev", addr_t);
      argv := DeclareParam (s, "args", addr_t);
      mode := DeclareParam (s, "mode", int_t);
      s.cg.begin_procedure(main);

      (* int argc;   void *envp;  *)
      argc := DeclareLocal (s, "argc", int_t);
      envp := DeclareLocal (s, "envp", addr_t);

      (* argc = -1; *)
      s.cg.set_source_line (src_line);  INC (src_line);
      s.cg.load_integer (int_t, TInt.MOne);
      s.cg.store (argc, 0, int_t, int_t);

      (* envp = (_ADDRESS)GetEnvironmentStringsA(); *)
      s.cg.set_source_line (src_line);  INC (src_line);
      s.cg.start_call_direct (getenv, 0, addr_t);
      s.cg.call_direct (getenv, addr_t);
      s.cg.store (envp, 0, addr_t, addr_t);

    ELSE (* not GUI *)
      (*
        int main (int argc, char **argv, char **envp)
      *)
      main := s.cg.declare_procedure (M3ID.Add("main"), (*n_params*) 3,
                                      int_t, (*lev*) 0,
                                      Target.DefaultCall, TRUE, NIL);
      argc := DeclareParam (s, "argc", int_t);
      argv := DeclareParam (s, "argv", addr_t);
      envp := DeclareParam (s, "envp", addr_t);
      s.cg.begin_procedure(main);

      (* void * self; *)
      self := DeclareLocal (s, "self", addr_t);

      (* self = 0; *)
      s.cg.set_source_line (src_line);  INC (src_line);
      s.cg.load_integer (int_t, TInt.Zero);
      s.cg.store (self, 0, int_t, int_t);
    END; (* if GUI *)

    (* RTLinker__InitRuntime (argc, argv, envp, self); *)
    s.cg.set_source_line (src_line);  INC (src_line);
    s.cg.start_call_direct (run_proc, 0, Target.CGType.Void);
    IF Target.DefaultCall.args_left_to_right THEN
      s.cg.load (argc, 0, int_t, int_t);        (* argc *)
      s.cg.pop_param (int_t);
      s.cg.load (argv, 0, addr_t, addr_t);      (* argv *)
      s.cg.pop_param (addr_t);
      s.cg.load (envp, 0, addr_t, addr_t);      (* envp *)
      s.cg.pop_param (addr_t);
      s.cg.load (self, 0, addr_t, addr_t);      (* self *)
      s.cg.pop_param (addr_t);
    ELSE
      s.cg.load (self, 0, addr_t, addr_t);      (* self *)
      s.cg.pop_param (addr_t);
      s.cg.load (envp, 0, addr_t, addr_t);      (* envp *)
      s.cg.pop_param (addr_t);
      s.cg.load (argv, 0, addr_t, addr_t);      (* argv *)
      s.cg.pop_param (addr_t);
      s.cg.load (argc, 0, int_t,  int_t);       (* argc *)
      s.cg.pop_param (int_t);
    END;
    s.cg.call_direct (run_proc, Target.CGType.Void);

    GenAddUnitImports(s.main_units);
    GenAddUnits(s.top_units);
    GenAddUnits(s.main_units);

    (*  RTProcess.Exit (0); *)
    s.cg.set_source_line (src_line);  INC (src_line);
    s.cg.start_call_direct (exit_proc, 0, Target.CGType.Void);
    s.cg.load_integer (int_t, TInt.Zero);
    s.cg.pop_param (int_t);
    s.cg.call_direct (exit_proc, Target.CGType.Void);

    (*  return 0; *)
    s.cg.set_source_line (src_line);  INC (src_line);
    s.cg.load_integer (int_t, TInt.Zero);
    s.cg.exit_proc (int_t);
    s.cg.end_procedure (main);
    s.cg.end_unit ();
  END GenerateCGEntry;

PROCEDURE DeclareParam (VAR s: State;  nm: TEXT;  tipe: Target.CGType): M3CG.Var =
  BEGIN
    RETURN s.cg.declare_param (M3ID.Add (nm), TargetMap.CG_Bytes[tipe],
                               TargetMap.CG_Align_bytes [tipe],
                               tipe, (*typeUID*) 0, (*in_memory*) FALSE,
                               (*up_level*) FALSE, (*frequency*) M3CG.Always);
  END DeclareParam;

PROCEDURE DeclareLocal (VAR s: State;  nm: TEXT;  tipe: Target.CGType): M3CG.Var =
  BEGIN
    RETURN s.cg.declare_local (M3ID.Add (nm), TargetMap.CG_Bytes[tipe],
                               TargetMap.CG_Align_bytes [tipe],
                               tipe, (*typeUID*) 0, (*in_memory*) FALSE,
                               (*up_level*) FALSE, (*frequency*) M3CG.Always);
  END DeclareLocal;
------------------------------------------------------------------------

PROCEDURE Err (VAR s: State;  a, b, c, d: TEXT := NIL) =
  <*FATAL Wr.Failure, Thread.Alerted*>
  BEGIN
    IF (s.errors = NIL) THEN RETURN END;
    IF (a # NIL) THEN Wr.PutText (s.errors, a); END;
    IF (b # NIL) THEN Wr.PutText (s.errors, b); END;
    IF (c # NIL) THEN Wr.PutText (s.errors, c); END;
    IF (d # NIL) THEN Wr.PutText (s.errors, d); END;
  END Err;

PROCEDURE Out (VAR s: State;  a, b, c, d: TEXT := NIL) =
  <*FATAL Wr.Failure, Thread.Alerted*>
  BEGIN
    IF (a # NIL) THEN Wr.PutText (s.wr, a) END;
    IF (b # NIL) THEN Wr.PutText (s.wr, b) END;
    IF (c # NIL) THEN Wr.PutText (s.wr, c) END;
    IF (d # NIL) THEN Wr.PutText (s.wr, d) END;
  END Out;

PROCEDURE Debug (level: INTEGER; a, b, c, d: TEXT := NIL) =
  BEGIN
    IF debugLevel >= level THEN
      Msg (a, b, c, d);
    END;
  END Debug;

PROCEDURE Msg (a, b, c, d: TEXT := NIL) =
  <*FATAL Wr.Failure, Thread.Alerted*>
  BEGIN
    WITH wr = Stdio.stdout DO
      IF (a # NIL) THEN Wr.PutText (wr, a) END;
      IF (b # NIL) THEN Wr.PutText (wr, b) END;
      IF (c # NIL) THEN Wr.PutText (wr, c) END;
      IF (d # NIL) THEN Wr.PutText (wr, d) END;
      Wr.PutText(wr, Wr.EOL);
    END;
  END Msg;

BEGIN
  debugLevel := 0;
END MxGen.

interface M3ID is in: