--  Mcode back-end for ortho - Expressions and control handling.
--  Copyright (C) 2006 Tristan Gingold
--
--  GHDL 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 2, or (at your option) any later
--  version.
--
--  GHDL 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 GCC; see the file COPYING.  If not, write to the Free
--  Software Foundation, 59 Temple Place - Suite 330, Boston, MA
--  02111-1307, USA.
with Ada.Text_IO;
with Ada.Unchecked_Deallocation;
with GNAT.Table;
with Ortho_Code.Types; use Ortho_Code.Types;
with Ortho_Code.Consts; use Ortho_Code.Consts;
with Ortho_Code.Decls; use Ortho_Code.Decls;
with Ortho_Code.Debug; use Ortho_Code.Debug;
with Ortho_Code.Abi; use Ortho_Code.Abi;
with Ortho_Code.Disps;
with Ortho_Code.Opts;
with Ortho_Code.Flags;

package body Ortho_Code.Exprs is

   type Enode_Pad is mod 256;

   type Enode_Common is record
      Kind : OE_Kind; --  about 1 byte (6 bits)
      Reg : O_Reg; --  1 byte
      Mode : Mode_Type; -- 4 bits
      Ref : Boolean;
      Flag1 : Boolean;
      Flag2 : Boolean;
      Flag3 : Boolean;
      Pad : Enode_Pad;
      Arg1 : O_Enode;
      Arg2 : O_Enode;
      Info : Int32;
   end record;
   pragma Pack (Enode_Common);
   for Enode_Common'Size use 4*32;
   for Enode_Common'Alignment use 4;

   package Enodes is new GNAT.Table
     (Table_Component_Type => Enode_Common,
      Table_Index_Type => O_Enode,
      Table_Low_Bound => 2,
      Table_Initial => 1024,
      Table_Increment => 100);

   function Get_Expr_Kind (Enode : O_Enode) return OE_Kind is
   begin
      return Enodes.Table (Enode).Kind;
   end Get_Expr_Kind;

   function Get_Expr_Mode (Enode : O_Enode) return Mode_Type is
   begin
      return Enodes.Table (Enode).Mode;
   end Get_Expr_Mode;

   function Get_Enode_Type (Enode : O_Enode) return O_Tnode is
   begin
      return O_Tnode (Enodes.Table (Enode).Info);
   end Get_Enode_Type;

   function Get_Expr_Reg (Enode : O_Enode) return O_Reg is
   begin
      return Enodes.Table (Enode).Reg;
   end Get_Expr_Reg;

   procedure Set_Expr_Reg (Enode : O_Enode; Reg : O_Reg) is
   begin
      Enodes.Table (Enode).Reg := Reg;
   end Set_Expr_Reg;

   function Get_Expr_Operand (Enode : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Enode).Arg1;
   end Get_Expr_Operand;

   procedure Set_Expr_Operand (Enode : O_Enode; Val : O_Enode) is
   begin
      Enodes.Table (Enode).Arg1 := Val;
   end Set_Expr_Operand;

   function Get_Expr_Left (Enode : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Enode).Arg1;
   end Get_Expr_Left;

   function Get_Expr_Right (Enode : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Enode).Arg2;
   end Get_Expr_Right;

   procedure Set_Expr_Left (Enode : O_Enode; Val : O_Enode) is
   begin
      Enodes.Table (Enode).Arg1 := Val;
   end Set_Expr_Left;

   procedure Set_Expr_Right (Enode : O_Enode; Val : O_Enode) is
   begin
      Enodes.Table (Enode).Arg2 := Val;
   end Set_Expr_Right;

   function Get_Expr_Low (Cst : O_Enode) return Uns32 is
   begin
      return To_Uns32 (Int32 (Enodes.Table (Cst).Arg1));
   end Get_Expr_Low;

   function Get_Expr_High (Cst : O_Enode) return Uns32 is
   begin
      return To_Uns32 (Int32 (Enodes.Table (Cst).Arg2));
   end Get_Expr_High;

   function Get_Assign_Target (Enode : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Enode).Arg2;
   end Get_Assign_Target;

   procedure Set_Assign_Target (Enode : O_Enode; Targ : O_Enode) is
   begin
      Enodes.Table (Enode).Arg2 := Targ;
   end Set_Assign_Target;

   function Get_Expr_Lit (Lit : O_Enode) return O_Cnode is
   begin
      return O_Cnode (Enodes.Table (Lit).Arg1);
   end Get_Expr_Lit;

   function Get_Conv_Type (Enode : O_Enode) return O_Tnode is
   begin
      return O_Tnode (Enodes.Table (Enode).Arg2);
   end Get_Conv_Type;

   --  Leave node corresponding to the entry.
   function Get_Entry_Leave (Enode : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Enode).Arg1;
   end Get_Entry_Leave;

   procedure Set_Entry_Leave (Enode : O_Enode; Leave : O_Enode) is
   begin
      Enodes.Table (Enode).Arg1 := Leave;
   end Set_Entry_Leave;

   function Get_Jump_Label (Enode : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Enode).Arg2;
   end Get_Jump_Label;

   procedure Set_Jump_Label (Enode : O_Enode; Label : O_Enode) is
   begin
      Enodes.Table (Enode).Arg2 := Label;
   end Set_Jump_Label;

   function Get_Addr_Object (Enode : O_Enode) return O_Dnode is
   begin
      return O_Dnode (Enodes.Table (Enode).Arg1);
   end Get_Addr_Object;

   function Get_Addrl_Frame (Enode : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Enode).Arg2;
   end Get_Addrl_Frame;

   procedure Set_Addrl_Frame (Enode : O_Enode; Frame : O_Enode) is
   begin
      Enodes.Table (Enode).Arg2 := Frame;
   end Set_Addrl_Frame;

   function Get_Call_Subprg (Enode : O_Enode) return O_Dnode is
   begin
      return O_Dnode (Enodes.Table (Enode).Arg1);
   end Get_Call_Subprg;

   function Get_Stack_Adjust (Enode : O_Enode) return Int32 is
   begin
      return Int32 (Enodes.Table (Enode).Arg1);
   end Get_Stack_Adjust;

   function Get_Arg_Link (Enode : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Enode).Arg2;
   end Get_Arg_Link;

   function Get_Block_Decls (Blk : O_Enode) return O_Dnode is
   begin
      return O_Dnode (Enodes.Table (Blk).Arg2);
   end Get_Block_Decls;

   function Get_Block_Parent (Blk : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Blk).Arg1;
   end Get_Block_Parent;

   function Get_Block_Has_Alloca (Blk : O_Enode) return Boolean is
   begin
      return Enodes.Table (Blk).Flag1;
   end Get_Block_Has_Alloca;

   procedure Set_Block_Has_Alloca (Blk : O_Enode; Flag : Boolean) is
   begin
      Enodes.Table (Blk).Flag1 := Flag;
   end Set_Block_Has_Alloca;

   function Get_End_Beg (Blk : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Blk).Arg1;
   end Get_End_Beg;

   function Get_Label_Info (Label : O_Enode) return Int32 is
   begin
      return Int32 (Enodes.Table (Label).Arg2);
   end Get_Label_Info;

   procedure Set_Label_Info (Label : O_Enode; Info : Int32) is
   begin
      Enodes.Table (Label).Arg2 := O_Enode (Info);
   end Set_Label_Info;

   function Get_Label_Block (Label : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Label).Arg1;
   end Get_Label_Block;

   function Get_Spill_Info (Spill : O_Enode) return Int32 is
   begin
      return Int32 (Enodes.Table (Spill).Arg2);
   end Get_Spill_Info;

   procedure Set_Spill_Info (Spill : O_Enode; Info : Int32) is
   begin
      Enodes.Table (Spill).Arg2 := O_Enode (Info);
   end Set_Spill_Info;

   --  Get the statement link.
   function Get_Stmt_Link (Stmt : O_Enode) return O_Enode is
   begin
      return O_Enode (Enodes.Table (Stmt).Info);
   end Get_Stmt_Link;

   procedure Set_Stmt_Link (Stmt : O_Enode; Next : O_Enode) is
   begin
      Enodes.Table (Stmt).Info := Int32 (Next);
   end Set_Stmt_Link;

   function Get_BB_Next (Stmt : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Stmt).Arg1;
   end Get_BB_Next;
   pragma Unreferenced (Get_BB_Next);

   procedure Set_BB_Next (Stmt : O_Enode; Next : O_Enode) is
   begin
      Enodes.Table (Stmt).Arg1 := Next;
   end Set_BB_Next;

   function Get_BB_Number (Stmt : O_Enode) return Int32 is
   begin
      return Int32 (Enodes.Table (Stmt).Arg2);
   end Get_BB_Number;

   function Get_Loop_Level (Stmt : O_Enode) return Int32 is
   begin
      return Int32 (Enodes.Table (Stmt).Arg1);
   end Get_Loop_Level;

   procedure Set_Loop_Level (Stmt : O_Enode; Level : Int32) is
   begin
      Enodes.Table (Stmt).Arg1 := O_Enode (Level);
   end Set_Loop_Level;

   procedure Set_Case_Branch (C : O_Enode; Branch : O_Enode) is
   begin
      Enodes.Table (C).Arg2 := Branch;
   end Set_Case_Branch;

   procedure Set_Case_Branch_Choice (Branch : O_Enode; Choice : O_Enode) is
   begin
      Enodes.Table (Branch).Arg1 := Choice;
   end Set_Case_Branch_Choice;

   function Get_Case_Branch_Choice (Branch : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Branch).Arg1;
   end Get_Case_Branch_Choice;

   procedure Set_Case_Choice_Link (Choice : O_Enode; N_Choice : O_Enode) is
   begin
      Enodes.Table (Choice).Info := Int32 (N_Choice);
   end Set_Case_Choice_Link;

   function Get_Case_Choice_Link (Choice : O_Enode) return O_Enode is
   begin
      return O_Enode (Enodes.Table (Choice).Info);
   end Get_Case_Choice_Link;

   function Get_Ref_Field (Ref : O_Enode) return O_Fnode is
   begin
      return O_Fnode (Enodes.Table (Ref).Arg2);
   end Get_Ref_Field;

   function Get_Ref_Index (Ref : O_Enode) return O_Enode is
   begin
      return Enodes.Table (Ref).Arg2;
   end Get_Ref_Index;

   function Get_Expr_Line_Number (Stmt : O_Enode) return Int32 is
   begin
      return Int32 (Enodes.Table (Stmt).Arg1);
   end Get_Expr_Line_Number;

   function Get_Intrinsic_Operation (Stmt : O_Enode) return Int32 is
   begin
      return Int32 (Enodes.Table (Stmt).Arg1);
   end Get_Intrinsic_Operation;

   Last_Stmt : O_Enode := O_Enode_Null;

   procedure Link_Stmt (Stmt : O_Enode) is
   begin
      if Last_Stmt = O_Enode_Null then
         raise Program_Error;
      end if;
      Set_Stmt_Link (Last_Stmt, Stmt);
      Last_Stmt := Stmt;
   end Link_Stmt;

   function New_Enode (Kind : OE_Kind;
                       Rtype : O_Tnode;
                       Arg1 : O_Enode;
                       Arg2 : O_Enode) return O_Enode
   is
      Mode : Mode_Type;
   begin
      Mode := Get_Type_Mode (Rtype);
      Enodes.Append (Enode_Common'(Kind => Kind,
                                   Reg => 0,
                                   Mode => Mode,
                                   Ref => False,
                                   Flag1 => False,
                                   Flag2 => False,
                                   Flag3 => False,
                                   Pad => 0,
                                   Arg1 => Arg1,
                                   Arg2 => Arg2,
                                   Info => Int32 (Rtype)));
      return Enodes.Last;
   end New_Enode;

   function New_Enode (Kind : OE_Kind;
                       Mode : Mode_Type;
                       Rtype : O_Tnode;
                       Arg1 : O_Enode;
                       Arg2 : O_Enode) return O_Enode
   is
   begin
      Enodes.Append (Enode_Common'(Kind => Kind,
                                   Reg => 0,
                                   Mode => Mode,
                                   Ref => False,
                                   Flag1 => False,
                                   Flag2 => False,
                                   Flag3 => False,
                                   Pad => 0,
                                   Arg1 => Arg1,
                                   Arg2 => Arg2,
                                   Info => Int32 (Rtype)));
      return Enodes.Last;
   end New_Enode;

   procedure New_Enode_Stmt (Kind : OE_Kind; Arg1 : O_Enode; Arg2 : O_Enode)
   is
   begin
      Enodes.Append (Enode_Common'(Kind => Kind,
                                   Reg => 0,
                                   Mode => Mode_Nil,
                                   Ref => False,
                                   Flag1 => False,
                                   Flag2 => False,
                                   Flag3 => False,
                                   Pad => 0,
                                   Arg1 => Arg1,
                                   Arg2 => Arg2,
                                   Info => 0));
      Link_Stmt (Enodes.Last);
   end New_Enode_Stmt;

   procedure New_Enode_Stmt
     (Kind : OE_Kind; Mode : Mode_Type; Arg1 : O_Enode; Arg2 : O_Enode)
   is
   begin
      Enodes.Append (Enode_Common'(Kind => Kind,
                                   Reg => 0,
                                   Mode => Mode,
                                   Ref => False,
                                   Flag1 => False,
                                   Flag2 => False,
                                   Flag3 => False,
                                   Pad => 0,
                                   Arg1 => Arg1,
                                   Arg2 => Arg2,
                                   Info => 0));
      Link_Stmt (Enodes.Last);
   end New_Enode_Stmt;

   Bb_Num : Int32 := 0;
   Last_Bb : O_Enode := O_Enode_Null;

   procedure Create_BB is
   begin
      New_Enode_Stmt (OE_BB, Mode_Nil, O_Enode_Null, O_Enode (Bb_Num));
      if Last_Bb /= O_Enode_Null then
         Set_BB_Next (Last_Bb, Enodes.Last);
      end if;
      Last_Bb := Enodes.Last;
      Bb_Num := Bb_Num + 1;
   end Create_BB;

   procedure Start_BB is
   begin
      if Flags.Flag_Opt_BB then
         Create_BB;
      end if;
   end Start_BB;
   pragma Inline (Start_BB);

   procedure Check_Ref (E : O_Enode) is
   begin
      if Enodes.Table (E).Ref then
         raise Syntax_Error;
      end if;
      Enodes.Table (E).Ref := True;
   end Check_Ref;

   procedure Check_Ref (E : O_Lnode) is
   begin
      Check_Ref (O_Enode (E));
   end Check_Ref;

   procedure Check_Value_Type (Val : O_Enode; Vtype : O_Tnode) is
   begin
      if Get_Enode_Type (Val) /= Vtype then
         raise Syntax_Error;
      end if;
   end Check_Value_Type;

   function New_Const_U32 (Val : Uns32; Vtype : O_Tnode) return O_Enode
   is
   begin
      return New_Enode (OE_Const, Vtype,
                        O_Enode (To_Int32 (Val)), O_Enode_Null);
   end New_Const_U32;

   Last_Decl : O_Dnode := 2;
   Cur_Block : O_Enode := O_Enode_Null;

   procedure Start_Declare_Stmt
   is
      Res : O_Enode;
   begin
      New_Enode_Stmt (OE_Beg, Cur_Block, O_Enode_Null);
      Res := Enodes.Last;
      Enodes.Table (Res).Arg2 := O_Enode
        (Ortho_Code.Decls.Start_Declare_Stmt);
      Cur_Block := Res;
   end Start_Declare_Stmt;

   function New_Stack (Rtype : O_Tnode) return O_Enode is
   begin
      return New_Enode (OE_Get_Stack, Rtype, O_Enode_Null, O_Enode_Null);
   end New_Stack;

   procedure New_Stack_Restore (Blk : O_Enode)
   is
      Save_Asgn : O_Enode;
      Save_Var : O_Dnode;
   begin
      Save_Asgn := Get_Stmt_Link (Blk);
      Save_Var := Get_Addr_Object (Get_Assign_Target (Save_Asgn));
      New_Enode_Stmt (OE_Set_Stack, New_Value (New_Obj (Save_Var)),
                      O_Enode_Null);
   end New_Stack_Restore;

   procedure Finish_Declare_Stmt
   is
      Parent : O_Dnode;
   begin
      if Get_Block_Has_Alloca (Cur_Block) then
         New_Stack_Restore (Cur_Block);
      end if;
      New_Enode_Stmt (OE_End, Cur_Block, O_Enode_Null);
      Cur_Block := Get_Block_Parent (Cur_Block);
      if Cur_Block = O_Enode_Null then
         Parent := O_Dnode_Null;
      else
         Parent := Get_Block_Decls (Cur_Block);
      end if;
      Ortho_Code.Decls.Finish_Declare_Stmt (Parent);
   end Finish_Declare_Stmt;

   function New_Label return O_Enode is
   begin
      return New_Enode (OE_Label, Mode_Nil, O_Tnode_Null,
                        Cur_Block, O_Enode_Null);
   end New_Label;

   procedure Start_Subprogram_Body (Func : O_Dnode)
   is
      Start : O_Enode;
      D_Body : O_Dnode;
      Data : Subprogram_Data_Acc;
   begin
      if Cur_Subprg = null then
         Abi.Start_Body (Func);
      end if;

      Start := New_Enode (OE_Entry, Mode_Nil, O_Tnode_Null,
                          Last_Stmt, O_Enode_Null);
      D_Body := Decls.Start_Subprogram_Body (Func, Start);

      --  Create the corresponding decl.
      Enodes.Table (Start).Arg2 := O_Enode (D_Body);

      --  Create the data record.
      Data := new Subprogram_Data'(Parent => Cur_Subprg,
                                   First_Child => null,
                                   Last_Child => null,
                                   Brother => null,
                                   Depth => Get_Decl_Depth (Func),
                                   D_Decl => Func,
                                   E_Entry => Start,
                                   D_Body => D_Body,
                                   Exit_Label => O_Enode_Null,
                                   Last_Stmt => O_Enode_Null,
                                   Stack_Max => 0);

      if not Flag_Debug_Hli then
         Data.Exit_Label := New_Label;
      end if;

      --  Link the record.
      if Cur_Subprg = null then
         --  A top-level subprogram.
         if First_Subprg = null then
            First_Subprg := Data;
         else
            Last_Subprg.Brother := Data;
         end if;
         Last_Subprg := Data;
      else
         --  A nested subprogram.
         if Cur_Subprg.First_Child = null then
            Cur_Subprg.First_Child := Data;
         else
            Cur_Subprg.Last_Child.Brother := Data;
         end if;
         Cur_Subprg.Last_Child := Data;

         --  Also save last_stmt.
         Cur_Subprg.Last_Stmt := Last_Stmt;
      end if;

      Cur_Subprg := Data;
      Last_Stmt := Start;

      Start_Declare_Stmt;

      --  Create a basic block for the beginning of the subprogram.
      Start_BB;

      --  Disp declarations.
      if Cur_Subprg.Parent = null then
         if Ortho_Code.Debug.Flag_Debug_Body
           or Ortho_Code.Debug.Flag_Debug_Code
         then
            while Last_Decl <= D_Body loop
               case Get_Decl_Kind (Last_Decl) is
                  when OD_Block =>
                     --  Skip blocks.
                     Disp_Decl (1, Last_Decl);
                     Last_Decl := Get_Block_Last (Last_Decl) + 1;
                  when others =>
                     Disp_Decl (1, Last_Decl);
                     Last_Decl := Last_Decl + 1;
               end case;
            end loop;
         end if;
      end if;
   end Start_Subprogram_Body;

   procedure Finish_Subprogram_Body
   is
      Parent : Subprogram_Data_Acc;
   begin
      Finish_Declare_Stmt;

      --  Create a new basic block for the epilog.
      Start_BB;

      if not Flag_Debug_Hli then
         Link_Stmt (Cur_Subprg.Exit_Label);
      end if;

      New_Enode_Stmt (OE_Leave, O_Enode_Null, O_Enode_Null);

      --  Save last statement.
      Cur_Subprg.Last_Stmt := Enodes.Last;
      --  Set Leave of Entry.
      Set_Entry_Leave (Cur_Subprg.E_Entry, Enodes.Last);

      Decls.Finish_Subprogram_Body;

      Parent := Cur_Subprg.Parent;

      if Flags.Flag_Optimize then
         Opts.Optimize_Subprg (Cur_Subprg);
      end if;

      if Parent = null then
         --  This is a top-level subprogram.
         if Ortho_Code.Debug.Flag_Disp_Code then
            Disps.Disp_Subprg (Cur_Subprg);
         end if;
         if Ortho_Code.Debug.Flag_Dump_Code then
            Disp_Subprg_Body (1, Cur_Subprg.E_Entry);
         end if;
         if not Ortho_Code.Debug.Flag_Debug_Dump then
            Abi.Finish_Body (Cur_Subprg);
         end if;
      end if;

      --  Restore Cur_Subprg.
      Cur_Subprg := Parent;

      --  Restore Last_Stmt.
      if Cur_Subprg = null then
         Last_Stmt := O_Enode_Null;
      else
         Last_Stmt := Cur_Subprg.Last_Stmt;
      end if;
   end Finish_Subprogram_Body;

   function Get_Inner_Alloca (Label : O_Enode) return O_Enode
   is
      Res : O_Enode := O_Enode_Null;
      Blk : O_Enode;
      Last_Blk : constant O_Enode := Get_Label_Block (Label);
   begin
      Blk := Cur_Block;
      while Blk /= Last_Blk loop
         if Get_Block_Has_Alloca (Blk) then
            Res := Blk;
         end if;
         Blk := Get_Block_Parent (Blk);
      end loop;
      return Res;
   end Get_Inner_Alloca;

   procedure Emit_Jmp (Code : OE_Kind; Expr : O_Enode; Label : O_Enode)
   is
   begin
      --  Discard jump after jump.
      if Code /= OE_Jump or else Get_Expr_Kind (Last_Stmt) /= OE_Jump then
         New_Enode_Stmt (Code, Expr, Label);
      end if;
   end Emit_Jmp;


   --  If there is stack allocated memory to be freed, free it.
   --  Then jump to LABEL.
   procedure New_Allocb_Jump (Label : O_Enode)
   is
      Inner_Alloca : O_Enode;
   begin
      Inner_Alloca := Get_Inner_Alloca (Label);
      if Inner_Alloca /= O_Enode_Null then
         New_Stack_Restore (Inner_Alloca);
      end if;
      Emit_Jmp (OE_Jump, O_Enode_Null, Label);
   end New_Allocb_Jump;

   function New_Lit (Lit : O_Cnode) return O_Enode
   is
      L_Type : O_Tnode;
      H, L : Uns32;
   begin
      L_Type := Get_Const_Type (Lit);
      if Flag_Debug_Hli then
         return New_Enode (OE_Lit, L_Type, O_Enode (Lit), O_Enode_Null);
      else
         case Get_Const_Kind (Lit) is
            when OC_Signed
              | OC_Unsigned
              | OC_Float
              | OC_Null
              | OC_Lit =>
               Get_Const_Bytes (Lit, H, L);
               return New_Enode
                 (OE_Const, L_Type,
                  O_Enode (To_Int32 (L)), O_Enode (To_Int32 (H)));
            when OC_Address
              | OC_Subprg_Address =>
               return New_Enode (OE_Addrg, L_Type,
                                 O_Enode (Get_Const_Decl (Lit)), O_Enode_Null);
            when OC_Array
              | OC_Record
              | OC_Union
              | OC_Sizeof
              | OC_Alignof =>
               raise Syntax_Error;
         end case;
      end if;
   end New_Lit;

   function Get_Static_Chain (Depth : O_Depth) return O_Enode
   is
      Cur_Depth : O_Depth := Cur_Subprg.Depth;
      Subprg : Subprogram_Data_Acc;
      Res : O_Enode;
   begin
      if Depth = Cur_Depth then
         return New_Enode (OE_Get_Frame, Abi.Mode_Ptr, O_Tnode_Ptr,
                           O_Enode_Null, O_Enode_Null);
      else
         Subprg := Cur_Subprg;
         Res := O_Enode_Null;
         loop
            --  The static chain is the first interface of the subprogram.
            Res := New_Enode (OE_Addrl, Abi.Mode_Ptr, O_Tnode_Ptr,
                              O_Enode (Get_Subprg_Interfaces (Subprg.D_Decl)),
                              Res);
            Res := New_Enode (OE_Indir, O_Tnode_Ptr, Res, O_Enode_Null);
            Cur_Depth := Cur_Depth - 1;
            if Cur_Depth = Depth then
               return Res;
            end if;
            Subprg := Subprg.Parent;
         end loop;
      end if;
   end Get_Static_Chain;

   function New_Obj (Obj : O_Dnode) return O_Lnode
   is
      O_Type : O_Tnode;
      Kind : OE_Kind;
      Chain : O_Enode;
      Depth : O_Depth;
   begin
      O_Type := Get_Decl_Type (Obj);
      case Get_Decl_Kind (Obj) is
         when OD_Local
           | OD_Interface =>
            Kind := OE_Addrl;
            --  Local declarations are 1 deeper than their subprogram.
            Depth := Get_Decl_Depth (Obj) - 1;
            if Depth /= Cur_Subprg.Depth then
               Chain := Get_Static_Chain (Depth);
            else
               Chain := O_Enode_Null;
            end if;
         when OD_Var
           | OD_Const =>
            Kind := OE_Addrg;
            Chain := O_Enode_Null;
         when others =>
            raise Program_Error;
      end case;
      return O_Lnode (New_Enode (Kind, Abi.Mode_Ptr, O_Type,
                                 O_Enode (Obj), Chain));
   end New_Obj;

   function New_Dyadic_Op (Kind : ON_Dyadic_Op_Kind; Left, Right : O_Enode)
                          return O_Enode
   is
      L_Type : O_Tnode;
   begin
      L_Type := Get_Enode_Type (Left);
      if Flag_Debug_Assert then
         if L_Type /= Get_Enode_Type (Right) then
            raise Syntax_Error;
         end if;
         if Get_Type_Mode (L_Type) = Mode_Blk then
            raise Syntax_Error;
         end if;
         Check_Ref (Left);
         Check_Ref (Right);
      end if;

      return New_Enode (OE_Kind'Val (ON_Op_Kind'Pos (Kind)),
                        L_Type, Left, Right);
   end New_Dyadic_Op;

   function New_Monadic_Op (Kind : ON_Monadic_Op_Kind; Operand : O_Enode)
                           return O_Enode
   is
      O_Type : O_Tnode;
   begin
      O_Type := Get_Enode_Type (Operand);

      if Flag_Debug_Assert then
         if Get_Type_Mode (O_Type) = Mode_Blk then
            raise Syntax_Error;
         end if;
         Check_Ref (Operand);
      end if;

      return New_Enode (OE_Kind'Val (ON_Op_Kind'Pos (Kind)), O_Type,
                        Operand, O_Enode_Null);
   end New_Monadic_Op;

   function New_Compare_Op
     (Kind : ON_Compare_Op_Kind; Left, Right : O_Enode; Ntype : O_Tnode)
     return O_Enode
   is
      Res : O_Enode;
   begin
      if Flag_Debug_Assert then
         if Get_Enode_Type (Left) /= Get_Enode_Type (Right) then
            raise Syntax_Error;
         end if;
         if Get_Expr_Mode (Left) = Mode_Blk then
            raise Syntax_Error;
         end if;
         if Get_Type_Kind (Ntype) /= OT_Boolean then
            raise Syntax_Error;
         end if;
         Check_Ref (Left);
         Check_Ref (Right);
      end if;

      Res := New_Enode (OE_Kind'Val (ON_Op_Kind'Pos (Kind)), Ntype,
                        Left, Right);
      if Flag_Debug_Hli then
         return New_Enode (OE_Typed, Ntype, Res, O_Enode (Ntype));
      else
         return Res;
      end if;
   end New_Compare_Op;

   function New_Sizeof (Atype : O_Tnode; Rtype : O_Tnode) return O_Enode is
   begin
      return New_Const_U32 (Get_Type_Size (Atype), Rtype);
   end New_Sizeof;

   function New_Offsetof (Field : O_Fnode; Rtype : O_Tnode) return O_Enode is
   begin
      return New_Const_U32 (Get_Field_Offset (Field), Rtype);
   end New_Offsetof;

   function Is_Pow2 (V : Uns32) return Boolean is
   begin
      return (V and -V) = V;
   end Is_Pow2;

   function Extract_Pow2 (V : Uns32) return Uns32 is
   begin
      for I in Natural range 0 .. 31 loop
         if V = Shift_Left (1, I) then
            return Uns32 (I);
         end if;
      end loop;
      raise Program_Error;
   end Extract_Pow2;

   function New_Index_Slice_Element
     (Arr : O_Lnode; Index : O_Enode; Res_Type : O_Tnode)
     return O_Lnode
   is
      El_Type : O_Tnode;
      In_Type : O_Tnode;
      Sz : O_Enode;
      El_Size : Uns32;
   begin
      El_Type := Get_Type_Array_Element (Get_Enode_Type (O_Enode (Arr)));
      In_Type := Get_Enode_Type (Index);

      if Flag_Debug_Assert then
         Check_Ref (Index);
         Check_Ref (Arr);
      end if;

      --  result := arr + index * sizeof (element).
      El_Size := Get_Type_Size (El_Type);
      if El_Size = 1 then
         Sz := Index;
      elsif Get_Expr_Kind (Index) = OE_Const then
         --  FIXME: may recycle previous index?
         Sz := New_Const_U32 (Get_Expr_Low (Index) * El_Size, In_Type);
      else
         if Is_Pow2 (El_Size) then
            Sz := New_Const_U32 (Extract_Pow2 (El_Size), In_Type);
            Sz := New_Enode (OE_Shl, In_Type, Index, Sz);
         else
            Sz := New_Const_U32 (El_Size, In_Type);
            Sz := New_Enode (OE_Mul, In_Type, Index, Sz);
         end if;
      end if;
      return O_Lnode (New_Enode (OE_Add, Abi.Mode_Ptr, Res_Type,
                                 O_Enode (Arr), Sz));
   end New_Index_Slice_Element;

   function New_Hli_Index_Slice
     (Kind : OE_Kind; Res_Type : O_Tnode; Arr : O_Lnode; Index : O_Enode)
     return O_Lnode
   is
   begin
      if Flag_Debug_Assert then
         Check_Ref (Index);
         Check_Ref (Arr);
      end if;
      return O_Lnode (New_Enode (Kind, Res_Type, O_Enode (Arr), Index));
   end New_Hli_Index_Slice;

   --  Get an element of an array.
   --  INDEX must be of the type of the array index.
   function New_Indexed_Element (Arr : O_Lnode; Index : O_Enode)
                                return O_Lnode
   is
      El_Type : O_Tnode;
   begin
      El_Type := Get_Type_Array_Element (Get_Enode_Type (O_Enode (Arr)));

      if Flag_Debug_Hli then
         return New_Hli_Index_Slice (OE_Index_Ref, El_Type, Arr, Index);
      else
         return New_Index_Slice_Element (Arr, Index, El_Type);
      end if;
   end New_Indexed_Element;

   --  Get a slice of an array; this is equivalent to a conversion between
   --  an array or an array subtype and an array subtype.
   --  RES_TYPE must be an array_sub_type whose base type is the same as the
   --  base type of ARR.
   --  INDEX must be of the type of the array index.
   function New_Slice (Arr : O_Lnode; Res_Type : O_Tnode; Index : O_Enode)
                      return O_Lnode
   is
   begin
      if Flag_Debug_Hli then
         return New_Hli_Index_Slice (OE_Slice_Ref, Res_Type, Arr, Index);
      else
         return New_Index_Slice_Element (Arr, Index, Res_Type);
      end if;
   end New_Slice;

   function New_Selected_Element (Rec : O_Lnode; El : O_Fnode)
                                 return O_Lnode
   is
      Offset : Uns32;
      Off : O_Enode;
      Res_Type : O_Tnode;
   begin
      if Flag_Debug_Assert then
         Check_Ref (Rec);
      end if;

      Res_Type := Get_Field_Type (El);
      if Flag_Debug_Hli then
         return O_Lnode (New_Enode (OE_Record_Ref, Res_Type,
                                    O_Enode (Rec), O_Enode (El)));
      else
         Offset := Get_Field_Offset (El);
         if Offset = 0 then
            return O_Lnode (New_Enode (OE_Conv_Ptr, Abi.Mode_Ptr, Res_Type,
                                       O_Enode (Rec), O_Enode (Res_Type)));
         else
            Off := New_Enode (OE_Const, Mode_U32, O_Tnode_Null,
                              O_Enode (Offset), O_Enode_Null);

            return O_Lnode (New_Enode (OE_Add, Abi.Mode_Ptr, Res_Type,
                                       O_Enode (Rec), Off));
         end if;
      end if;
   end New_Selected_Element;

   function New_Access_Element (Acc : O_Enode) return O_Lnode
   is
      Acc_Type : O_Tnode;
      Res_Type : O_Tnode;
   begin
      Acc_Type := Get_Enode_Type (Acc);

      if Flag_Debug_Assert then
         if Get_Type_Kind (Acc_Type) /= OT_Access then
            raise Syntax_Error;
         end if;
         Check_Ref (Acc);
      end if;

      Res_Type := Get_Type_Access_Type (Acc_Type);
      if Flag_Debug_Hli then
         return O_Lnode (New_Enode (OE_Access_Ref, Abi.Mode_Ptr, Res_Type,
                                    Acc, O_Enode_Null));
      else
         return O_Lnode (New_Enode (OE_Conv_Ptr, Abi.Mode_Ptr, Res_Type,
                                    Acc, O_Enode (Res_Type)));
      end if;
   end New_Access_Element;

   function New_Convert_Ov (Val : O_Enode; Rtype : O_Tnode) return O_Enode is
   begin
      if Flag_Debug_Assert then
         Check_Ref (Val);
      end if;

      return New_Enode (OE_Conv, Rtype, Val, O_Enode (Rtype));
   end New_Convert_Ov;

   function New_Unchecked_Address (Lvalue : O_Lnode; Atype : O_Tnode)
                                  return O_Enode is
   begin
      if Flag_Debug_Assert then
         if Get_Type_Kind (Atype) /= OT_Access then
            raise Syntax_Error;
         end if;
         Check_Ref (Lvalue);
      end if;

      return New_Enode (OE_Conv_Ptr, Abi.Mode_Ptr, Atype,
                        O_Enode (Lvalue), O_Enode (Atype));
   end New_Unchecked_Address;

   function New_Address (Lvalue : O_Lnode; Atype : O_Tnode) return O_Enode is
   begin
      if Flag_Debug_Assert then
         if Get_Type_Kind (Atype) /= OT_Access then
            raise Syntax_Error;
         end if;
         if Get_Base_Type (Get_Enode_Type (O_Enode (Lvalue)))
           /= Get_Base_Type (Get_Type_Access_Type (Atype))
         then
            raise Syntax_Error;
         end if;
         Check_Ref (Lvalue);
      end if;

      return New_Enode (OE_Conv_Ptr, Abi.Mode_Ptr, Atype,
                        O_Enode (Lvalue), O_Enode (Atype));
   end New_Address;

   function New_Subprogram_Address (Subprg : O_Dnode; Atype : O_Tnode)
                                   return O_Enode is
   begin
      raise Program_Error;
      return O_Enode_Null;
   end New_Subprogram_Address;

   function New_Value (Lvalue : O_Lnode) return O_Enode
   is
      V_Type : O_Tnode;
   begin
      V_Type := Get_Enode_Type (O_Enode (Lvalue));

      if Flag_Debug_Assert then
         Check_Ref (Lvalue);
      end if;

      return New_Enode (OE_Indir, V_Type, O_Enode (Lvalue), O_Enode_Null);
   end New_Value;

   function New_Alloca (Rtype : O_Tnode; Size : O_Enode) return O_Enode
   is
      Save_Var : O_Dnode;
      Stmt : O_Enode;
      St_Type : O_Tnode;
   begin
      if Flag_Debug_Assert then
         Check_Ref (Size);
         if Get_Type_Kind (Rtype) /= OT_Access then
            raise Syntax_Error;
         end if;
         if Get_Type_Kind (Get_Enode_Type (Size)) /= OT_Unsigned then
            raise Syntax_Error;
         end if;
      end if;

      if not Get_Block_Has_Alloca (Cur_Block) then
         Set_Block_Has_Alloca (Cur_Block, True);
         if Stack_Ptr_Type /= O_Tnode_Null then
            St_Type := Stack_Ptr_Type;
         else
            St_Type := Rtype;
         end if;
         --  Add a decl.
         New_Var_Decl (Save_Var, O_Ident_Nul, O_Storage_Local, St_Type);
         --  Add insn to save stack ptr.
         Stmt := New_Enode (OE_Asgn, St_Type,
                            New_Stack (St_Type),
                            O_Enode (New_Obj (Save_Var)));
         if Cur_Block = Last_Stmt then
            Set_Stmt_Link (Last_Stmt, Stmt);
            Last_Stmt := Stmt;
         else
            Set_Stmt_Link (Stmt, Get_Stmt_Link (Cur_Block));
            Set_Stmt_Link (Cur_Block, Stmt);
         end if;
      end if;

      return New_Enode (OE_Alloca, Rtype, Size, O_Enode (Rtype));
   end New_Alloca;

   procedure Start_Association (Assocs : out O_Assoc_List; Subprg : O_Dnode)
   is
      Depth : O_Depth;
      Arg : O_Enode;
      First_Inter : O_Dnode;
   begin
      First_Inter := Get_Subprg_Interfaces (Subprg);
      if Get_Decl_Storage (Subprg) = O_Storage_Local then
         Depth := Get_Decl_Depth (Subprg);
         Arg := New_Enode (OE_Arg, Abi.Mode_Ptr, O_Tnode_Ptr,
                           Get_Static_Chain (Depth - 1), O_Enode_Null);
         First_Inter := Get_Interface_Chain (First_Inter);
      else
         Arg := O_Enode_Null;
      end if;
      Assocs := (Subprg => Subprg,
                 First_Arg => Arg,
                 Last_Arg => Arg,
                 Next_Inter => First_Inter);
   end Start_Association;

   procedure New_Association (Assocs : in out O_Assoc_List; Val : O_Enode)
   is
      V_Type : O_Tnode;
      Mode : Mode_Type;
      N_Mode : Mode_Type;
      Res : O_Enode;
   begin
      V_Type := Get_Enode_Type (Val);

      if Flag_Debug_Assert then
         if Assocs.Next_Inter = O_Dnode_Null then
            --  More assocs than interfaces.
            raise Syntax_Error;
         end if;
         Check_Value_Type (Val, Get_Decl_Type (Assocs.Next_Inter));
         Check_Ref (Val);
      end if;

      --  Follow the C convention call: no parameters shorter than int.
      Mode := Get_Type_Mode (V_Type);
      case Mode is
         when Mode_B2
           | Mode_U8
           | Mode_U16 =>
            N_Mode := Mode_U32;
         when Mode_I8
           | Mode_I16 =>
            N_Mode := Mode_I32;
         when Mode_P32
           | Mode_U32
           | Mode_I32
           | Mode_U64
           | Mode_I64
           | Mode_P64
           | Mode_F32
           | Mode_F64 =>
            N_Mode := Mode;
         when Mode_Blk
           | Mode_Nil
           | Mode_X1 =>
            raise Program_Error;
      end case;
      if N_Mode /= Mode and not Flag_Debug_Hli then
         Res := New_Enode (OE_Conv, N_Mode, V_Type, Val, O_Enode (V_Type));
      else
         Res := Val;
      end if;
      Res := New_Enode (OE_Arg, N_Mode, V_Type, Res, O_Enode_Null);
      if Assocs.Last_Arg /= O_Enode_Null then
         Enodes.Table (Assocs.Last_Arg).Arg2 := Res;
      else
         Assocs.First_Arg := Res;
      end if;
      Assocs.Last_Arg := Res;
      Assocs.Next_Inter := Get_Interface_Chain (Assocs.Next_Inter);
   end New_Association;

   function New_Function_Call (Assocs : O_Assoc_List) return O_Enode
   is
      F_Type : O_Tnode;
   begin
      if Flag_Debug_Assert then
         if Assocs.Next_Inter /= O_Dnode_Null then
            --  Not enough assocs.
            raise Syntax_Error;
         end if;
      end if;

      F_Type := Get_Decl_Type (Assocs.Subprg);
      return New_Enode (OE_Call, F_Type,
                        O_Enode (Assocs.Subprg), Assocs.First_Arg);
   end New_Function_Call;

   procedure New_Procedure_Call (Assocs : in out O_Assoc_List) is
   begin
      if Flag_Debug_Assert then
         if Assocs.Next_Inter /= O_Dnode_Null then
            --  Not enough assocs.
            raise Syntax_Error;
         end if;
      end if;
      New_Enode_Stmt (OE_Call, O_Enode (Assocs.Subprg), Assocs.First_Arg);
   end New_Procedure_Call;

   procedure New_Assign_Stmt (Target : O_Lnode; Value : O_Enode)
   is
      V_Type : O_Tnode;
   begin
      V_Type := Get_Enode_Type (Value);

      if Flag_Debug_Assert then
         Check_Value_Type (Value, Get_Enode_Type (O_Enode (Target)));
         Check_Ref (Value);
         Check_Ref (Target);
      end if;

      New_Enode_Stmt (OE_Asgn, Get_Type_Mode (V_Type),
                      Value, O_Enode (Target));
   end New_Assign_Stmt;

   procedure New_Return_Stmt (Value : O_Enode)
   is
      V_Type : O_Tnode;
   begin
      V_Type := Get_Enode_Type (Value);

      if Flag_Debug_Assert then
         Check_Ref (Value);
         Check_Value_Type (Value, Get_Decl_Type (Cur_Subprg.D_Decl));
      end if;

      New_Enode_Stmt (OE_Ret, Get_Type_Mode (V_Type), Value, O_Enode_Null);
      if not Flag_Debug_Hli then
         New_Allocb_Jump (Cur_Subprg.Exit_Label);
      end if;
   end New_Return_Stmt;

   procedure New_Return_Stmt is
   begin
      if Flag_Debug_Assert then
         if Get_Decl_Kind (Cur_Subprg.D_Decl) /= OD_Procedure then
            raise Syntax_Error;
         end if;
      end if;

      if not Flag_Debug_Hli then
         New_Allocb_Jump (Cur_Subprg.Exit_Label);
      else
         New_Enode_Stmt (OE_Ret, Mode_Nil, O_Enode_Null, O_Enode_Null);
      end if;
   end New_Return_Stmt;


   procedure Start_If_Stmt (Block : out O_If_Block; Cond : O_Enode) is
   begin
      if Flag_Debug_Assert then
         if Get_Expr_Mode (Cond) /= Mode_B2 then
            --  COND must be a boolean.
            raise Syntax_Error;
         end if;
         Check_Ref (Cond);
      end if;

      if not Flag_Lower_Stmt then
         New_Enode_Stmt (OE_If, Cond, O_Enode_Null);
         Block := (Label_End => O_Enode_Null,
                   Label_Next => Last_Stmt);
      else
         Block := (Label_End => O_Enode_Null,
                   Label_Next => New_Label);
         Emit_Jmp (OE_Jump_F, Cond, Block.Label_Next);
         Start_BB;
      end if;
   end Start_If_Stmt;

   procedure New_Else_Stmt (Block : in out O_If_Block) is
   begin
      if not Flag_Lower_Stmt then
         New_Enode_Stmt (OE_Else, O_Enode_Null, O_Enode_Null);
      else
         if Block.Label_End = O_Enode_Null then
            Block.Label_End := New_Label;
         end if;
         Emit_Jmp (OE_Jump, O_Enode_Null, Block.Label_End);
         Start_BB;
         Link_Stmt (Block.Label_Next);
         Block.Label_Next := O_Enode_Null;
      end if;
   end New_Else_Stmt;

   procedure Finish_If_Stmt (Block : in out O_If_Block) is
   begin
      if not Flag_Lower_Stmt then
         New_Enode_Stmt (OE_Endif, O_Enode_Null, O_Enode_Null);
      else
         --  Create a badic-block after the IF.
         Start_BB;
         if Block.Label_Next /= O_Enode_Null then
            Link_Stmt (Block.Label_Next);
         end if;
         if Block.Label_End /= O_Enode_Null then
            Link_Stmt (Block.Label_End);
         end if;
      end if;
   end Finish_If_Stmt;

   procedure Start_Loop_Stmt (Label : out O_Snode) is
   begin
      if not Flag_Lower_Stmt then
         New_Enode_Stmt (OE_Loop, O_Enode_Null, O_Enode_Null);
         Label := (Label_Start => Last_Stmt,
                   Label_End => O_Enode_Null);
      else
         --  Create a basic-block at the beginning of the loop.
         Start_BB;
         Label.Label_Start := New_Label;
         Link_Stmt (Label.Label_Start);
         Label.Label_End := New_Label;
      end if;
   end Start_Loop_Stmt;

   procedure Finish_Loop_Stmt (Label : in out O_Snode)
   is
   begin
      if not Flag_Lower_Stmt then
         New_Enode_Stmt (OE_Eloop, Label.Label_Start, O_Enode_Null);
      else
         Emit_Jmp (OE_Jump, O_Enode_Null, Label.Label_Start);
         Start_BB;
         Link_Stmt (Label.Label_End);
      end if;
   end Finish_Loop_Stmt;

   procedure New_Exit_Stmt (L : O_Snode)
   is
   begin
      if not Flag_Lower_Stmt then
         New_Enode_Stmt (OE_Exit, O_Enode_Null, L.Label_Start);
      else
         New_Allocb_Jump (L.Label_End);
      end if;
   end New_Exit_Stmt;

   procedure New_Next_Stmt (L : O_Snode)
   is
   begin
      if not Flag_Lower_Stmt then
         New_Enode_Stmt (OE_Next, O_Enode_Null, L.Label_Start);
      else
         New_Allocb_Jump (L.Label_Start);
      end if;
   end New_Next_Stmt;

   procedure Start_Case_Stmt (Block : out O_Case_Block; Value : O_Enode)
   is
      V_Type : O_Tnode;
      Mode : Mode_Type;
      Start : O_Enode;
   begin
      V_Type := Get_Enode_Type (Value);
      Mode := Get_Type_Mode (V_Type);

      if Flag_Debug_Assert then
         Check_Ref (Value);
         case Mode is
            when Mode_U8 .. Mode_U64
              | Mode_I8 .. Mode_I64
              | Mode_B2 =>
               null;
            when others =>
               raise Syntax_Error;
         end case;
      end if;

      New_Enode_Stmt (OE_Case, Mode, Value, O_Enode_Null);
      Start := Enodes.Last;
      if Flag_Debug_Hli then
         Block := (Expr => Start,
                   Expr_Type => V_Type,
                   Last_Node => O_Enode_Null,
                   Label_End => O_Enode_Null,
                   Label_Branch => Start);
      else
         Block := (Expr => Start,
                   Expr_Type => V_Type,
                   Last_Node => Start,
                   Label_End => New_Label,
                   Label_Branch => O_Enode_Null);
      end if;
   end Start_Case_Stmt;

   procedure Start_Choice (Block : in out O_Case_Block)
   is
      B : O_Enode;
   begin
      if Flag_Debug_Hli then
         B := New_Enode (OE_Case_Branch, Mode_Nil, O_Tnode_Null,
                         O_Enode_Null, O_Enode_Null);
         Link_Stmt (B);
         --  Link it.
         Set_Case_Branch (Block.Label_Branch, B);
         Block.Label_Branch := B;
      else
         --  Jump to the end of the case statement.
         --  If there is already a branch open, this is ok
         --   (do not fall-through).
         --  If there is no branch open, then this is the default choice
         --   (nothing to do).
         Emit_Jmp (OE_Jump, O_Enode_Null, Block.Label_End);

         --  Create a label for the code of this branch.
         Block.Label_Branch := New_Label;
      end if;
   end Start_Choice;

   procedure Insert_Choice_Stmt (Block : in out O_Case_Block; Stmt : O_Enode)
   is
      Prev : O_Enode;
   begin
      Prev := Get_Stmt_Link (Block.Last_Node);
      Set_Stmt_Link (Block.Last_Node, Stmt);
      Block.Last_Node := Stmt;
      if Prev = O_Enode_Null then
         Last_Stmt := Stmt;
      else
         Set_Stmt_Link (Stmt, Prev);
      end if;
   end Insert_Choice_Stmt;

   procedure Emit_Choice_Jmp (Block : in out O_Case_Block;
                              Code : OE_Kind; Expr : O_Enode; Label : O_Enode)
   is
      Jmp : O_Enode;
   begin
      Jmp := New_Enode (Code, Mode_Nil, O_Tnode_Null, Expr, Label);
      Insert_Choice_Stmt (Block, Jmp);
   end Emit_Choice_Jmp;

   --  Create a node containing the value of the case expression.
   function New_Case_Expr (Block : O_Case_Block) return O_Enode is
   begin
      return New_Enode (OE_Case_Expr, Block.Expr_Type,
                        Block.Expr, O_Enode_Null);
   end New_Case_Expr;

   procedure New_Hli_Choice (Block : in out O_Case_Block;
                             Hi, Lo : O_Enode)
   is
      Res : O_Enode;
   begin
      Res := New_Enode (OE_Case_Choice, Mode_Nil, O_Tnode_Null, Hi, Lo);
      if Block.Label_End = O_Enode_Null then
         Set_Case_Branch_Choice (Block.Label_Branch, Res);
      else
         Set_Case_Choice_Link (Block.Label_End, Res);
      end if;
      Block.Label_End := Res;
   end New_Hli_Choice;

   procedure New_Expr_Choice (Block : in out O_Case_Block; Expr : O_Cnode)
   is
      Res : O_Enode;
   begin
      if Flag_Debug_Hli then
         New_Hli_Choice (Block, New_Lit (Expr), O_Enode_Null);
      else
         Res := New_Enode (OE_Eq, Mode_B2, O_Tnode_Null,
                           New_Case_Expr (Block), New_Lit (Expr));
         Emit_Choice_Jmp (Block, OE_Jump_T, Res, Block.Label_Branch);
      end if;
   end New_Expr_Choice;

   procedure New_Range_Choice (Block : in out O_Case_Block;
                               Low, High : O_Cnode)
   is
      E1 : O_Enode;
      E2 : O_Enode;
      Label : O_Enode;
   begin
      if Flag_Debug_Hli then
         New_Hli_Choice (Block, New_Lit (Low), New_Lit (High));
      else
         --  Internal label.
         Label := New_Label;
         E1 := New_Enode (OE_Lt, Mode_B2, O_Tnode_Null,
                          New_Case_Expr (Block), New_Lit (Low));
         Emit_Choice_Jmp (Block, OE_Jump_T, E1, Label);
         E2 := New_Enode (OE_Le, Mode_B2, O_Tnode_Null,
                          New_Case_Expr (Block), New_Lit (High));
         Emit_Choice_Jmp (Block, OE_Jump_T, E2, Block.Label_Branch);
         Insert_Choice_Stmt (Block, Label);
      end if;
   end New_Range_Choice;

   procedure New_Default_Choice (Block : in out O_Case_Block) is
   begin
      if Flag_Debug_Hli then
         New_Hli_Choice (Block, O_Enode_Null, O_Enode_Null);
      else
         --  Jump to the code.
         Emit_Choice_Jmp (Block, OE_Jump, O_Enode_Null, Block.Label_Branch);
      end if;
   end New_Default_Choice;

   procedure Finish_Choice (Block : in out O_Case_Block) is
   begin
      if Flag_Debug_Hli then
         Block.Label_End := O_Enode_Null;
      else
         --  Put the label of the branch.
         Start_BB;
         Link_Stmt (Block.Label_Branch);
      end if;
   end Finish_Choice;

   procedure Finish_Case_Stmt (Block : in out O_Case_Block) is
   begin
      if Flag_Debug_Hli then
         New_Enode_Stmt (OE_Case_End, O_Enode_Null, O_Enode_Null);
      else
         --  Jump to the end of the case statement.
         --  Note: this is not required, since the next instruction is the
         --   label.
         --  Emit_Jmp (OE_Jump, O_Enode_Null, Block.Label_End);

         --  Put the label of the end of the case.
         Start_BB;
         Link_Stmt (Block.Label_End);
         Block.Label_End := O_Enode_Null;
      end if;
   end Finish_Case_Stmt;

   procedure New_Debug_Line_Stmt (Line : Natural) is
   begin
      New_Enode_Stmt (OE_Line, O_Enode (Line), O_Enode_Null);
   end New_Debug_Line_Stmt;

   procedure Debug_Expr (N : O_Enode)
   is
      use Ada.Text_IO;
      use Ortho_Code.Debug.Int32_IO;
      Indent : constant Count := Col;
   begin
      Put (Int32 (N), 0);
      Set_Col (Indent + 7);
      Disp_Mode (Get_Expr_Mode (N));
      Put ("  ");
      Put (OE_Kind'Image (Get_Expr_Kind (N)));
      Set_Col (Indent + 28);
--       Put (Abi.Image_Insn (Get_Expr_Insn (N)));
--       Put ("  ");
      Put (Abi.Image_Reg (Get_Expr_Reg (N)));
      Put ("  ");
      Put (Int32 (Enodes.Table (N).Arg1), 7);
      Put (Int32 (Enodes.Table (N).Arg2), 7);
      Put (Enodes.Table (N).Info, 7);
      New_Line;
   end Debug_Expr;

   procedure Disp_Subprg_Body (Indent : Natural; Subprg : O_Enode)
   is
      use Ada.Text_IO;
      N : O_Enode;
      N_Indent : Natural;
   begin
      N := Subprg;
      if Get_Expr_Kind (N) /= OE_Entry then
         raise Program_Error;
      end if;
      --  Display the entry.
      Set_Col (Count (Indent));
      Debug_Expr (N);
      --  Display the subprogram, binding.
      N_Indent := Indent;-- + 1;
      N := N + 1;
      loop
         case Get_Expr_Kind (N) is
            when OE_Entry =>
               N := Get_Entry_Leave (N) + 1;
            when OE_Leave =>
               Set_Col (Count (Indent));
               Debug_Expr (N);
               exit;
            when others =>
               Set_Col (Count (N_Indent));
               Debug_Expr (N);
               case Get_Expr_Kind (N) is
                  when OE_Beg =>
                     Disp_Block (N_Indent + 2,
                                 O_Dnode (Enodes.Table (N).Arg2));
                     N_Indent := N_Indent + 1;
                  when OE_End =>
                     N_Indent := N_Indent - 1;
                  when others =>
                     null;
               end case;
               N := N + 1;
         end case;
      end loop;
   end Disp_Subprg_Body;

   procedure Disp_All_Enode is
   begin
      for I in Enodes.First .. Enodes.Last loop
         Debug_Expr (I);
      end loop;
   end Disp_All_Enode;

   Max_Enode : O_Enode := O_Enode_Null;

   procedure Mark (M : out Mark_Type) is
   begin
      M.Enode := Enodes.Last;
   end Mark;

   procedure Release (M : Mark_Type) is
   begin
      Max_Enode := O_Enode'Max (Max_Enode, Enodes.Last);
      Enodes.Set_Last (M.Enode);
   end Release;

   procedure Disp_Stats
   is
      use Ada.Text_IO;
   begin
      Max_Enode := O_Enode'Max (Max_Enode, Enodes.Last);
      Put ("Number of Enodes:" & O_Enode'Image (Enodes.Last));
      Put (", max:" & O_Enode'Image (Max_Enode));
      New_Line;
   end Disp_Stats;

   procedure Free_Subprogram_Data (Data : in out Subprogram_Data_Acc)
   is
      procedure Free is new Ada.Unchecked_Deallocation
        (Subprogram_Data, Subprogram_Data_Acc);
      Ch, N_Ch : Subprogram_Data_Acc;
   begin
      Ch := Data.First_Child;
      while Ch /= null loop
         N_Ch := Ch.Brother;
         Free_Subprogram_Data (Ch);
         Ch := N_Ch;
      end loop;
      Free (Data);
   end Free_Subprogram_Data;

   procedure Finish is
   begin
      Enodes.Free;
      Free_Subprogram_Data (First_Subprg);
   end Finish;
end Ortho_Code.Exprs;