--  Values in synthesis.
--  Copyright (C) 2017 Tristan Gingold
--
--  This file is part of GHDL.
--
--  This program 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 of the License, or
--  (at your option) any later version.
--
--  This program 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 program.  If not, see <gnu.org/licenses>.

with Ada.Unchecked_Conversion;
with System; use System;

with Mutils; use Mutils;
with Types_Utils;

package body Elab.Vhdl_Objtypes is
   function To_Rec_El_Array_Acc is new Ada.Unchecked_Conversion
     (System.Address, Rec_El_Array_Acc);

   function To_Type_Acc is new Ada.Unchecked_Conversion
     (System.Address, Type_Acc);

   function "+" (L, R : Value_Offsets) return Value_Offsets is
   begin
      return (L.Net_Off + R.Net_Off, L.Mem_Off + R.Mem_Off);
   end "+";

   function Is_Bounded_Type (Typ : Type_Acc) return Boolean is
   begin
      case Typ.Kind is
         when Type_Bit
            | Type_Logic
            | Type_Discrete
            | Type_Float
            | Type_Vector
            | Type_Slice
            | Type_Array
            | Type_Record
            | Type_Access
            | Type_File =>
            return True;
         when Type_Unbounded_Array
            | Type_Array_Unbounded
            | Type_Unbounded_Vector
            | Type_Unbounded_Record
            | Type_Protected =>
            return False;
      end case;
   end Is_Bounded_Type;

   function Are_Types_Equal (L, R : Type_Acc) return Boolean is
   begin
      if L.Kind /= R.Kind
        or else L.W /= R.W
      then
         return False;
      end if;
      if L = R then
         return True;
      end if;

      case L.Kind is
         when Type_Bit
           | Type_Logic =>
            return True;
         when Type_Discrete =>
            return L.Drange = R.Drange;
         when Type_Float =>
            return L.Frange = R.Frange;
         when Type_Array
            | Type_Array_Unbounded
            | Type_Vector =>
            if L.Alast /= R.Alast then
               return False;
            end if;
            if L.Abound /= R.Abound then
               return False;
            end if;
            return Are_Types_Equal (L.Arr_El, R.Arr_El);
         when Type_Unbounded_Array
            | Type_Unbounded_Vector =>
            if L.Ulast /= R.Ulast then
               return False;
            end if;
            --  Also check index ?
            return Are_Types_Equal (L.Uarr_El, R.Uarr_El);
         when Type_Slice =>
            return Are_Types_Equal (L.Slice_El, R.Slice_El);
         when Type_Record
           | Type_Unbounded_Record =>
            if L.Rec.Len /= R.Rec.Len then
               return False;
            end if;
            for I in L.Rec.E'Range loop
               if not Are_Types_Equal (L.Rec.E (I).Typ, R.Rec.E (I).Typ) then
                  return False;
               end if;
            end loop;
            return True;
         when Type_Access =>
            return Are_Types_Equal (L.Acc_Acc, R.Acc_Acc);
         when Type_File =>
            return Are_Types_Equal (L.File_Typ, R.File_Typ);
         when Type_Protected =>
            return False;
      end case;
   end Are_Types_Equal;

   function Is_Last_Dimension (Arr : Type_Acc) return Boolean is
   begin
      case Arr.Kind is
         when Type_Vector
            | Type_Array
            | Type_Array_Unbounded =>
            return Arr.Alast;
         when Type_Unbounded_Vector =>
            return True;
         when Type_Unbounded_Array =>
            return Arr.Ulast;
         when others =>
            raise Internal_Error;
      end case;
   end Is_Last_Dimension;

   function Is_Null_Range (Rng : Discrete_Range_Type) return Boolean is
   begin
      case Rng.Dir is
         when Dir_To =>
            return Rng.Left > Rng.Right;
         when Dir_Downto =>
            return Rng.Left < Rng.Right;
      end case;
   end Is_Null_Range;

   function Is_Scalar_Subtype_Compatible (L, R : Type_Acc) return Boolean is
   begin
      pragma Assert (L.Kind = R.Kind);
      case L.Kind is
         when Type_Bit
           | Type_Logic =>
            --  We have no bounds for that...
            return True;
         when Type_Discrete =>
            if Is_Null_Range (L.Drange) then
               return True;
            end if;
            return In_Range (R.Drange, L.Drange.Left)
              and then In_Range (R.Drange, L.Drange.Right);
         when Type_Float =>
            return L.Frange = R.Frange;
         when others =>
            raise Internal_Error;
      end case;
   end Is_Scalar_Subtype_Compatible;

   function Discrete_Range_Width (Rng : Discrete_Range_Type) return Uns32
   is
      Lo, Hi : Int64;
      W : Uns32;
   begin
      case Rng.Dir is
         when Dir_To =>
            Lo := Rng.Left;
            Hi := Rng.Right;
         when Dir_Downto =>
            Lo := Rng.Right;
            Hi := Rng.Left;
      end case;
      if Lo > Hi then
         --  Null range.
         W := 0;
      elsif Lo >= 0 then
         --  Positive.
         W := Uns32 (Clog2 (Uns64 (Hi) + 1));
      elsif Lo = Int64'First then
         --  Handle possible overflow.
         W := 64;
      elsif Hi < 0 then
         --  Negative only.
         W := Uns32 (Clog2 (Uns64 (-Lo))) + 1;
      else
         declare
            Wl : constant Uns32 := Uns32 (Clog2 (Uns64 (-Lo)));
            Wh : constant Uns32 := Uns32 (Clog2 (Uns64 (Hi) + 1));
         begin
            W := Uns32'Max (Wl, Wh) + 1;
         end;
      end if;
      return W;
   end Discrete_Range_Width;

   function In_Bounds (Bnd : Bound_Type; V : Int32) return Boolean is
   begin
      case Bnd.Dir is
         when Dir_To =>
            return V >= Bnd.Left and then V <= Bnd.Right;
         when Dir_Downto =>
            return V <= Bnd.Left and then V >= Bnd.Right;
      end case;
   end In_Bounds;

   function In_Range (Rng : Discrete_Range_Type; V : Int64) return Boolean is
   begin
      case Rng.Dir is
         when Dir_To =>
            return V >= Rng.Left and then V <= Rng.Right;
         when Dir_Downto =>
            return V <= Rng.Left and then V >= Rng.Right;
      end case;
   end In_Range;

   function In_Float_Range (Rng : Float_Range_Type; V : Fp64) return Boolean is
   begin
      case Rng.Dir is
         when Dir_To =>
            return V >= Rng.Left and then V <= Rng.Right;
         when Dir_Downto =>
            return V <= Rng.Left and then V >= Rng.Right;
      end case;
   end In_Float_Range;

   function Build_Discrete_Range_Type
     (L : Int64; R : Int64; Dir : Direction_Type) return Discrete_Range_Type is
   begin
      return (Dir => Dir,
              Left => L,
              Right => R,
              Is_Signed => L < 0 or R < 0);
   end Build_Discrete_Range_Type;

   procedure Realign (Res : in out Size_Type;
                      Align : Size_Type) is
   begin
      Res := (Res + Align - 1) and not (Align - 1);
   end Realign;

   --  For Compute_Size_Type.
   procedure Add_Size_Type (Typ : Type_Acc;
                            Sz : in out Size_Type;
                            Align : in out Size_Type);

   procedure Add_Array_Size_Type (El_Typ : Type_Acc;
                                  Sz : in out Size_Type;
                                  Align : in out Size_Type)
   is
      subtype T is Type_Type (Type_Array);
   begin
      Align := Size_Type'Max (Align, T'Alignment);
      Realign (Sz, Align);
      Sz := Sz + (T'Size / System.Storage_Unit);
      Add_Size_Type (El_Typ, Sz, Align);
   end Add_Array_Size_Type;

   procedure Add_Size_Type (Typ : Type_Acc;
                            Sz : in out Size_Type;
                            Align : in out Size_Type) is
   begin
      case Typ.Kind is
         when Type_Bit
            | Type_Logic
            | Type_Discrete
            | Type_Float =>
            --  Never copied.
            return;
         when Type_Access
            | Type_File
            | Type_Protected =>
            --  Never copied
            return;
         when Type_Array
            | Type_Array_Unbounded
            | Type_Vector =>
            Add_Array_Size_Type (Typ.Arr_El, Sz, Align);
         when Type_Unbounded_Array
            | Type_Unbounded_Vector =>
            Add_Array_Size_Type (Typ.Uarr_El, Sz, Align);
         when Type_Record
            | Type_Unbounded_Record =>
            declare
               subtype T is Type_Type (Type_Record);
               subtype T_El is Rec_El_Array (Typ.Rec.Len);
            begin
               --  The type
               Align := Size_Type'Max (Align, T'Alignment);
               Realign (Sz, Align);
               Sz := Sz + (T'Size / System.Storage_Unit);
               --  The el array
               Align := Size_Type'Max (Align, T_El'Alignment);
               Realign (Sz, Align);
               Sz := Sz + (T_El'Size / System.Storage_Unit);
               --  The elements
               for I in Typ.Rec.E'Range loop
                  Add_Size_Type (Typ.Rec.E (I).Typ, Sz, Align);
               end loop;
            end;
         when Type_Slice =>
            raise Internal_Error;
      end case;
   end Add_Size_Type;

   --  Compute the memory size needed to store T.
   function Compute_Size_Type (T : Type_Acc) return Size_Type
   is
      Align : Size_Type;
      Size : Size_Type;
   begin
      Size := 0;
      Align := 1;
      Add_Size_Type (T, Size, Align);
      return Size;
   end Compute_Size_Type;

   function Create_Bit_Subtype (Rng : Discrete_Range_Type) return Type_Acc
   is
      subtype Bit_Type_Type is Type_Type (Type_Bit);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Bit_Type_Type);
   begin
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Bit,
                                                Wkind => Wkind_Net,
                                                Drange => Rng,
                                                Is_Global => False,
                                                Is_Static => True,
                                                Is_Bnd_Static => True,
                                                Al => 0,
                                                Sz => 1,
                                                W => 1)));
   end Create_Bit_Subtype;

   function Create_Logic_Subtype (Rng : Discrete_Range_Type) return Type_Acc
   is
      subtype Logic_Type_Type is Type_Type (Type_Logic);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Logic_Type_Type);
   begin
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Logic,
                                                Wkind => Wkind_Net,
                                                Drange => Rng,
                                                Is_Global => False,
                                                Is_Static => True,
                                                Is_Bnd_Static => True,
                                                Al => 0,
                                                Sz => 1,
                                                W => 1)));
   end Create_Logic_Subtype;

   function Create_Discrete_Type (Rng : Discrete_Range_Type;
                                  Sz : Size_Type;
                                  W : Uns32)
                                 return Type_Acc
   is
      subtype Discrete_Type_Type is Type_Type (Type_Discrete);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Discrete_Type_Type);
      Al : Palign_Type;
   begin
      if Sz <= 1 then
         Al := 0;
      elsif Sz <= 4 then
         Al := 2;
      else
         pragma Assert (Sz <= 8);
         case Uns64'Alignment is
            when 8 =>
               Al := 3;
            when 4 =>
               Al := 2;
            when others =>
               raise Internal_Error;
         end case;
      end if;
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Discrete,
                                                Wkind => Wkind_Net,
                                                Al => Al,
                                                Is_Global => False,
                                                Is_Static => True,
                                                Is_Bnd_Static => True,
                                                Sz => Sz,
                                                W => W,
                                                Drange => Rng)));
   end Create_Discrete_Type;

   function Create_Float_Type (Rng : Float_Range_Type) return Type_Acc
   is
      subtype Float_Type_Type is Type_Type (Type_Float);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Float_Type_Type);
      Al : Palign_Type;
   begin
      case Fp64'Alignment is
         when 8 =>
            Al := 3;
         when 4 =>
            Al := 2;
         when others =>
            raise Internal_Error;
      end case;

      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Float,
                                                Wkind => Wkind_Net,
                                                Al => Al,
                                                Is_Global => False,
                                                Is_Static => True,
                                                Is_Bnd_Static => True,
                                                Sz => 8,
                                                W => 64,
                                                Frange => Rng)));
   end Create_Float_Type;

   function Create_Vector_Type (Bnd : Bound_Type;
                                Static_Bnd : Boolean;
                                El_Type : Type_Acc) return Type_Acc
   is
      subtype Vector_Type_Type is Type_Type (Type_Vector);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Vector_Type_Type);
   begin
      pragma Assert (El_Type.Kind in Type_Nets);
      return To_Type_Acc
        (Alloc (Current_Pool, (Kind => Type_Vector,
                               Wkind => El_Type.Wkind,
                               Al => El_Type.Al,
                               Is_Global => False,
                               Is_Static => Static_Bnd,
                               Is_Bnd_Static => Static_Bnd,
                               Sz => El_Type.Sz * Size_Type (Bnd.Len),
                               W => Bnd.Len,
                               Alast => True,
                               Abound => Bnd,
                               Arr_El => El_Type)));
   end Create_Vector_Type;

   function Create_Slice_Type
     (Base_Type : Type_Acc; Len : Uns32; El_Type : Type_Acc) return Type_Acc
   is
      subtype Slice_Type_Type is Type_Type (Type_Slice);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Slice_Type_Type);
   begin
      return To_Type_Acc (Alloc (Current_Pool,
                                 (Kind => Type_Slice,
                                  Wkind => El_Type.Wkind,
                                  Al => El_Type.Al,
                                  Is_Global => False,
                                  Is_Static => False,
                                  Is_Bnd_Static => False,
                                  Sz => Size_Type (Len) * El_Type.Sz,
                                  W => Len * El_Type.W,
                                  Slice_Base => Base_Type,
                                  Slice_Len => Len,
                                  Slice_El => El_Type)));
   end Create_Slice_Type;

   function Create_Vec_Type_By_Length (Len : Uns32; El : Type_Acc)
                                      return Type_Acc is
   begin
      return Create_Vector_Type ((Dir => Dir_Downto,
                                  Left => Int32 (Len) - 1,
                                  Right => 0,
                                  Len => Len),
                                 False,
                                 El);
   end Create_Vec_Type_By_Length;

   function Create_Array_Type (Bnd : Bound_Type;
                               Static_Bnd : Boolean;
                               Last : Boolean;
                               El_Type : Type_Acc) return Type_Acc
   is
      subtype Array_Type_Type is Type_Type (Type_Array);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Array_Type_Type);
      Is_Static : constant Boolean := Static_Bnd and El_Type.Is_Static;
   begin
      return To_Type_Acc (Alloc (Current_Pool,
                                 (Kind => Type_Array,
                                  Wkind => El_Type.Wkind,
                                  Al => El_Type.Al,
                                  Is_Global => False,
                                  Is_Static => Is_Static,
                                  Is_Bnd_Static => Static_Bnd,
                                  Sz => El_Type.Sz * Size_Type (Bnd.Len),
                                  W => El_Type.W * Bnd.Len,
                                  Abound => Bnd,
                                  Alast => Last,
                                  Arr_El => El_Type)));
   end Create_Array_Type;

   function Create_Array_Unbounded_Type (Bnd : Bound_Type;
                                         Static_Bnd : Boolean;
                                         Last : Boolean;
                                         El_Type : Type_Acc) return Type_Acc
   is
      subtype Array_Unbounded_Type_Type is Type_Type (Type_Array_Unbounded);
      function Alloc is
         new Areapools.Alloc_On_Pool_Addr (Array_Unbounded_Type_Type);
   begin
      return To_Type_Acc (Alloc (Current_Pool,
                                 (Kind => Type_Array_Unbounded,
                                  Wkind => El_Type.Wkind,
                                  Al => El_Type.Al,
                                  Is_Global => False,
                                  Is_Static => False,
                                  Is_Bnd_Static => Static_Bnd,
                                  Sz => 0,
                                  W => 0,
                                  Abound => Bnd,
                                  Alast => Last,
                                  Arr_El => El_Type)));
   end Create_Array_Unbounded_Type;

   function Create_Unbounded_Array
     (Idx : Type_Acc; Last : Boolean; El_Type : Type_Acc) return Type_Acc
   is
      subtype Unbounded_Type_Type is Type_Type (Type_Unbounded_Array);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Unbounded_Type_Type);
   begin
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Unbounded_Array,
                                                Wkind => El_Type.Wkind,
                                                Al => El_Type.Al,
                                                Is_Global => False,
                                                Is_Static => False,
                                                Is_Bnd_Static => False,
                                                Sz => 0,
                                                W => 0,
                                                Ulast => Last,
                                                Uarr_El => El_Type,
                                                Uarr_Idx => Idx)));
   end Create_Unbounded_Array;

   function Create_Array_From_Array_Unbounded
     (Parent : Type_Acc; El : Type_Acc) return Type_Acc is
   begin
      if Parent.Alast then
         return Create_Array_Type
           (Parent.Abound, Parent.Is_Bnd_Static, True, El);
      else
         return Create_Array_Type
           (Parent.Abound, Parent.Is_Bnd_Static, False,
            Create_Array_From_Array_Unbounded (Parent.Arr_El, El));
      end if;
   end Create_Array_From_Array_Unbounded;

   function Create_Unbounded_Vector (El_Type : Type_Acc; Idx : Type_Acc)
                                    return Type_Acc
   is
      subtype Unbounded_Type_Type is Type_Type (Type_Unbounded_Vector);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Unbounded_Type_Type);
   begin
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Unbounded_Vector,
                                                Wkind => El_Type.Wkind,
                                                Al => El_Type.Al,
                                                Is_Global => False,
                                                Is_Static => False,
                                                Is_Bnd_Static => False,
                                                Sz => 0,
                                                W => 0,
                                                Ulast => True,
                                                Uarr_El => El_Type,
                                                Uarr_Idx => Idx)));
   end Create_Unbounded_Vector;

   function Get_Array_Element (Arr_Type : Type_Acc) return Type_Acc is
   begin
      case Arr_Type.Kind is
         when Type_Vector
           | Type_Array
           | Type_Array_Unbounded =>
            return Arr_Type.Arr_El;
         when Type_Unbounded_Array
           | Type_Unbounded_Vector =>
            return Arr_Type.Uarr_El;
         when others =>
            raise Internal_Error;
      end case;
   end Get_Array_Element;

   function Get_Array_Bound (Typ : Type_Acc) return Bound_Type is
   begin
      case Type_Vectors_Arrays (Typ.Kind) is
         when Type_Vector
           | Type_Array_Unbounded
           | Type_Array =>
            return Typ.Abound;
         when others =>
            raise Internal_Error;
      end case;
   end Get_Array_Bound;

   function Get_Uarray_Index (Typ : Type_Acc) return Type_Acc is
   begin
      case Typ.Kind is
         when Type_Unbounded_Vector
           | Type_Unbounded_Array =>
            return Typ.Uarr_Idx;
         when others =>
            raise Internal_Error;
      end case;
   end Get_Uarray_Index;

   function Get_Range_Length (Rng : Discrete_Range_Type) return Uns32
   is
      Len : Int64;
   begin
      case Rng.Dir is
         when Dir_To =>
            Len := Rng.Right - Rng.Left + 1;
         when Dir_Downto =>
            Len := Rng.Left - Rng.Right + 1;
      end case;
      if Len < 0 then
         return 0;
      elsif Len > Int64 (Uns32'Last) then
         --  Truncate very large lengths, such objects should not exist.
         return Uns32'Last;
      else
         return Uns32 (Len);
      end if;
   end Get_Range_Length;

   function Create_Rec_El_Array (Nels : Iir_Index32; Pool : Areapool_Acc)
                                return Rec_El_Array_Acc
   is
      subtype Data_Type is Rec_El_Array (Nels);
      Res : Address;
   begin
      --  Manually allocate the array to handle large arrays without
      --  creating a large temporary value.
      Areapools.Allocate
        (Pool.all, Res,
         Data_Type'Size / Storage_Unit, Data_Type'Alignment);

      declare
         --  Discard the warnings for no pragma Import as we really want
         --  to use the default initialization.
         pragma Warnings (Off);
         Addr1 : constant Address := Res;
         Init : Data_Type;
         for Init'Address use Addr1;
         pragma Warnings (On);
      begin
         null;
      end;

      return To_Rec_El_Array_Acc (Res);
   end Create_Rec_El_Array;

   function Create_Rec_El_Array (Nels : Iir_Index32) return Rec_El_Array_Acc is
   begin
      return Create_Rec_El_Array (Nels, Current_Pool);
   end Create_Rec_El_Array;

   function Align (Off : Size_Type; Al : Palign_Type) return Size_Type
   is
      Mask : constant Size_Type := 2 ** Natural (Al) - 1;
   begin
      return (Off + Mask) and not Mask;
   end Align;

   procedure Layout_Element_Mem (El : in out Rec_El_Type;
                                 Sz : in out Size_Type;
                                 Al : in out Palign_Type) is
   begin
      --  For memory.
      Al := Palign_Type'Max (Al, El.Typ.Al);
      Sz := Align (Sz, El.Typ.Al);
      El.Offs.Mem_Off := Sz;
      Sz := Sz + El.Typ.Sz;
   end Layout_Element_Mem;

   procedure Layout_Element_Net (El : in out Rec_El_Type;
                                 W : in out Uns32;
                                 Wkind : in out Wkind_Type) is
   begin
      --  For nets.
      El.Offs.Net_Off := W;
      if El.Typ.Wkind /= Wkind_Net then
         Wkind := Wkind_Undef;
      end if;
      W := W + El.Typ.W;
   end Layout_Element_Net;

   function Create_Record_Type (Parent_Typ : Type_Acc;
                                Els : Rec_El_Array_Acc) return Type_Acc
   is
      subtype Record_Type_Type is Type_Type (Type_Record);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Record_Type_Type);
      Base : Type_Acc;
      Base_Els : Rec_El_Array_Acc;
      Wkind : Wkind_Type;
      W : Uns32;
      Al : Palign_Type;
      Sz : Size_Type;
      Res : Type_Acc;
      Is_Static : Boolean;
   begin
      --  Layout the record.
      if Parent_Typ = null then
         Base := null;
         Base_Els := null;
      else
         Base := Parent_Typ.Rec_Base;
         Base_Els := Base.Rec;
      end if;

      Al := 0;
      Sz := 0;
      Is_Static := True;
      --  First elements with static types...
      for I in Els.E'Range loop
         declare
            El : Rec_El_Type renames Els.E (I);
            El_Static : Boolean;
         begin
            if Base_Els = null then
               El_Static := El.Typ.Is_Static;
            else
               El_Static := Base_Els.E (I).Typ.Is_Static;
            end if;

            if El_Static then
               Layout_Element_Mem (El, Sz, Al);
            else
               Is_Static := False;
            end if;
         end;
      end loop;

      --  ... then the others.
      if not Is_Static then
         for I in Els.E'Range loop
            declare
               El : Rec_El_Type renames Els.E (I);
               El_Static : Boolean;
            begin
               if Base_Els = null then
                  El_Static := El.Typ.Is_Static;
               else
                  El_Static := Base_Els.E (I).Typ.Is_Static;
               end if;

               if not El_Static then
                  Layout_Element_Mem (El, Sz, Al);
               end if;
            end;
         end loop;
      end if;

      Sz := Align (Sz, Al);

      --  Layout nets.
      Wkind := Wkind_Net;
      W := 0;
      for I in Els.E'Range loop
         Layout_Element_Net (Els.E (I), W, Wkind);
      end loop;

      Res := To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Record,
                                                Wkind => Wkind,
                                                Al => Al,
                                                Is_Global => False,
                                                Is_Static => Is_Static,
                                                Is_Bnd_Static => False,
                                                Sz => Sz,
                                                W => W,
                                                Rec_Base => null,
                                                Rec => Els)));
      if Parent_Typ = null then
         Res.Rec_Base := Res;
      else
         Res.Rec_Base := Base;
      end if;

      return Res;
   end Create_Record_Type;

   function Create_Unbounded_Record (Parent_Typ : Type_Acc;
                                     Els : Rec_El_Array_Acc) return Type_Acc
   is
      subtype Unbounded_Record_Type_Type is Type_Type (Type_Unbounded_Record);
      function Alloc is
         new Areapools.Alloc_On_Pool_Addr (Unbounded_Record_Type_Type);
      Base : Type_Acc;
      Base_Els : Rec_El_Array_Acc;
      Wkind : Wkind_Type;
      W : Uns32;
      Al : Palign_Type;
      Sz : Size_Type;
      Res : Type_Acc;
   begin
      --  Layout the record.
      Wkind := Wkind_Net;
      W := 0;
      if Parent_Typ = null then
         --  Layout only static elements.
         Al := 0;
         Sz := 0;
         for I in Els.E'Range loop
            declare
               El : Rec_El_Type renames Els.E (I);
            begin
               if El.Typ.Is_Static then
                  Layout_Element_Mem (El, Sz, Al);
                  El.Offs.Net_Off := 0;
               else
                  El.Offs := No_Value_Offsets;
               end if;
            end;
         end loop;
      else
         --  Copy layout of base type.
         Base := Parent_Typ.Rec_Base;
         Base_Els := Base.Rec;
         Al := Base.Al;
         Sz := Base.Sz;
         for I in Els.E'Range loop
            Els.E (I).Offs := Base_Els.E (I).Offs;
         end loop;
      end if;

      Res := To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Unbounded_Record,
                                                Wkind => Wkind,
                                                Al => Al,
                                                Is_Global => False,
                                                Is_Static => False,
                                                Is_Bnd_Static => False,
                                                Sz => Sz,
                                                W => W,
                                                Rec_Base => null,
                                                Rec => Els)));
      if Parent_Typ = null then
         Res.Rec_Base := Res;
      else
         Res.Rec_Base := Parent_Typ.Rec_Base;
      end if;
      return Res;
   end Create_Unbounded_Record;

   --  Compute size and alignment for bounds of TYP.
   procedure Update_Bounds_Size (Typ : Type_Acc;
                                 Sz : in out Size_Type;
                                 Al : in out Palign_Type);

   procedure Update_Layout_Size (Typ : Type_Acc;
                                 Sz : in out Size_Type;
                                 Al : in out Palign_Type) is
   begin
      case Typ.Kind is
         when Type_Scalars
           | Type_Array
           | Type_Vector
           | Type_Record
           | Type_Access =>
            null;
         when Type_Unbounded_Vector
            | Type_Unbounded_Array
            | Type_Array_Unbounded =>
            declare
               B_Sz : Size_Type;
               B_Al : Palign_Type;
            begin
               --  Layout of an array is sizes + bounds.
               B_Sz := 2 * Ghdl_Index_Sz;
               B_Al := Ghdl_Index_Al;
               Update_Bounds_Size (Typ, B_Sz, B_Al);
               Sz := Align (Sz, B_Al);
               Sz := Sz + B_Sz;
               Al := Palign_Type'Max (Al, B_Al);
            end;
         when Type_Unbounded_Record =>
            --  Same as bounds.
            Update_Bounds_Size (Typ, Sz, Al);
         when Type_Slice
           | Type_File
           | Type_Protected =>
            raise Internal_Error;
      end case;
   end Update_Layout_Size;

   procedure Update_Bounds_Size (Typ : Type_Acc;
                                 Sz : in out Size_Type;
                                 Al : in out Palign_Type) is
   begin
      case Typ.Kind is
         when Type_Scalars
           | Type_Array
           | Type_Vector
           | Type_Record
           | Type_Access =>
            null;
         when Type_Array_Unbounded =>
            Update_Bounds_Size (Typ.Arr_El, Sz, Al);
         when Type_Unbounded_Array
           | Type_Unbounded_Vector =>
            declare
               Idx : constant Type_Acc := Typ.Uarr_Idx;
               B_Sz : Size_Type;
               B_Al : Palign_Type;
            begin
               --  Compute size of left, right and dir fields.
               case Idx.Sz is
                  when 1 =>
                     B_Sz := 3;
                     B_Al := 0;
                  when 4 =>
                     B_Sz := 9;
                     B_Al := 2;
                  when 8 =>
                     B_Sz := 17;
                     B_Al := 2;
                  when others =>
                     raise Internal_Error;
               end case;
               --  Add length field.
               Sz := Align (Sz, Ghdl_Index_Al);
               B_Sz := B_Sz + Ghdl_Index_Sz;
               --  Compute whole alignment.
               B_Al := Palign_Type'Max (3, Ghdl_Index_Al);
               B_Sz := Align (B_Sz, B_Al);
               --  Add to the result.
               Sz := Align (Sz, B_Al);
               Sz := Sz + B_Sz;

               if not Typ.Ulast then
                  --  Continue with next index.
                  Update_Bounds_Size (Typ.Uarr_El, Sz, Al);
               else
                  --  Continue with the element.
                  Update_Layout_Size (Typ.Uarr_El, Sz, Al);
               end if;

            end;
         when Type_Unbounded_Record =>
            declare
               B_Sz : Size_Type;
               B_Al : Palign_Type;
            begin
               --  Layout of an array is sizes + bounds.
               B_Sz := 2 * Ghdl_Index_Sz;
               B_Al := Ghdl_Index_Al;
               for I in Typ.Rec.E'Range loop
                  declare
                     El : Rec_El_Type renames Typ.Rec.E (I);
                  begin
                     if not El.Typ.Is_Static then
                        --  Add offset field (alignment is ok).
                        B_Sz := B_Sz + Ghdl_Index_Sz;
                        Update_Layout_Size (El.Typ, B_Sz, B_Al);
                     end if;
                  end;
               end loop;
               Sz := Align (Sz, B_Al);
               Sz := Sz + B_Sz;
               Al := Palign_Type'Max (Al, B_Al);
            end;
         when Type_Slice
           | Type_File
           | Type_Protected =>
            raise Internal_Error;
      end case;
   end Update_Bounds_Size;

   function Compute_Bounds_Size (Typ : Type_Acc) return Size_Type
   is
      Res : Size_Type;
      Al : Palign_Type;
   begin
      Res := 0;
      Al := 0;
      Update_Bounds_Size (Typ, Res, Al);
      return Res;
   end Compute_Bounds_Size;

   function Create_Access_Type (Parent_Type : Type_Acc; Acc_Type : Type_Acc)
                               return Type_Acc
   is
      subtype Access_Type_Type is Type_Type (Type_Access);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Access_Type_Type);
      Type_Sz : Size_Type;
      Bnd_Sz : Size_Type;
   begin
      if Acc_Type = null then
         --  For incomplete type.
         pragma Assert (Parent_Type = null);
         Type_Sz := 0;
         Bnd_Sz := 0;
      else
         Type_Sz := Compute_Size_Type (Acc_Type);
         if Parent_Type = null then
            Bnd_Sz := Compute_Bounds_Size (Acc_Type);
         else
            Bnd_Sz := Parent_Type.Acc_Bnd_Sz;
         end if;
      end if;
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Access,
                                                Wkind => Wkind_Sim,
                                                Al => Heap_Ptr_Al,
                                                Is_Global => False,
                                                Is_Static => True,
                                                Is_Bnd_Static => True,
                                                Sz => Heap_Ptr_Sz,
                                                W => 1,
                                                Acc_Acc => Acc_Type,
                                                Acc_Type_Sz => Type_Sz,
                                                Acc_Bnd_Sz => Bnd_Sz)));
   end Create_Access_Type;

   procedure Complete_Access_Type (Acc_Type : Type_Acc; Des_Typ : Type_Acc) is
   begin
      Acc_Type.Acc_Acc := Des_Typ;
      Acc_Type.Acc_Type_Sz := Compute_Size_Type (Des_Typ);
      Acc_Type.Acc_Bnd_Sz := Compute_Bounds_Size (Des_Typ);
   end Complete_Access_Type;

   function Create_File_Type (File_Type : Type_Acc) return Type_Acc
   is
      subtype File_Type_Type is Type_Type (Type_File);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (File_Type_Type);
   begin
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_File,
                                                Wkind => Wkind_Sim,
                                                Al => 2,
                                                Is_Global => False,
                                                Is_Static => True,
                                                Is_Bnd_Static => True,
                                                Sz => 4,
                                                W => 1,
                                                File_Typ => File_Type,
                                                File_Signature => null)));
   end Create_File_Type;

   function Create_Protected_Type return Type_Acc
   is
      subtype Protected_Type_Type is Type_Type (Type_Protected);
      function Alloc is new Areapools.Alloc_On_Pool_Addr (Protected_Type_Type);
   begin
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Protected,
                                                Wkind => Wkind_Sim,
                                                Al => 2,
                                                Is_Global => False,
                                                Is_Static => True,
                                                Is_Bnd_Static => True,
                                                Sz => 4,
                                                W => 1)));
   end Create_Protected_Type;

   function Vec_Length (Typ : Type_Acc) return Iir_Index32 is
   begin
      return Iir_Index32 (Typ.Abound.Len);
   end Vec_Length;

   function Get_Array_Flat_Length (Typ : Type_Acc) return Iir_Index32 is
   begin
      case Type_Vectors_Arrays (Typ.Kind) is
         when Type_Vector =>
            return Iir_Index32 (Typ.Abound.Len);
         when Type_Array
           | Type_Array_Unbounded =>
            declare
               Len : Uns32;
               T : Type_Acc;
            begin
               Len := 1;
               T := Typ;
               loop
                  Len := Len * T.Abound.Len;
                  exit when T.Alast;
                  T := T.Arr_El;
               end loop;
               return Iir_Index32 (Len);
            end;
         when others =>
            raise Internal_Error;
      end case;
   end Get_Array_Flat_Length;

   function Get_Type_Width (Atype : Type_Acc) return Uns32 is
   begin
      pragma Assert (Atype.Kind /= Type_Unbounded_Array);
      return Atype.W;
   end Get_Type_Width;

   function Get_Bound_Length (T : Type_Acc) return Uns32 is
   begin
      case T.Kind is
         when Type_Vector
           | Type_Array =>
            return T.Abound.Len;
         when Type_Slice =>
            return T.W;
         when others =>
            raise Internal_Error;
      end case;
   end Get_Bound_Length;

   function Is_Matching_Bounds (L, R : Type_Acc) return Boolean is
   begin
      case L.Kind is
         when Type_Bit
           | Type_Logic
           | Type_Discrete
           | Type_Float =>
            pragma Assert (L.Kind = R.Kind);
            return True;
         when Type_Vector
           | Type_Slice =>
            return Get_Bound_Length (L) = Get_Bound_Length (R);
         when Type_Array =>
            pragma Assert (L.Alast = R.Alast);
            if Get_Bound_Length (L) /= Get_Bound_Length (R) then
               return False;
            end if;
            if L.Alast then
               return True;
            end if;
            return Get_Bound_Length (L.Arr_El) = Get_Bound_Length (R.Arr_El);
         when Type_Array_Unbounded
            | Type_Unbounded_Array
            | Type_Unbounded_Vector
            | Type_Unbounded_Record =>
            raise Internal_Error;
         when Type_Record =>
            --  FIXME: handle vhdl-08
            return True;
         when Type_Access =>
            return True;
         when Type_File
           |  Type_Protected =>
            raise Internal_Error;
      end case;
   end Is_Matching_Bounds;

   function Read_U8 (Mt : Memtyp) return Ghdl_U8
   is
      pragma Assert (Mt.Typ.Sz = 1);
   begin
      return Read_U8 (Mt.Mem);
   end Read_U8;


   function Read_Fp64 (Mt : Memtyp) return Fp64 is
   begin
      return Read_Fp64 (Mt.Mem);
   end Read_Fp64;

   function Read_Discrete (Mem : Memory_Ptr; Typ : Type_Acc) return Int64 is
   begin
      case Typ.Sz is
         when 1 =>
            return Int64 (Read_U8 (Mem));
         when 4 =>
            return Int64 (Read_I32 (Mem));
         when 8 =>
            return Int64 (Read_I64 (Mem));
         when others =>
            raise Internal_Error;
      end case;
   end Read_Discrete;

   function Read_Discrete (Mt : Memtyp) return Int64 is
   begin
      return Read_Discrete (Mt.Mem, Mt.Typ);
   end Read_Discrete;

   procedure Write_Discrete (Mem : Memory_Ptr; Typ : Type_Acc; Val : Int64) is
   begin
      case Typ.Sz is
         when 1 =>
            Write_U8 (Mem, Ghdl_U8 (Val));
         when 4 =>
            Write_I32 (Mem, Ghdl_I32 (Val));
         when 8 =>
            Write_I64 (Mem, Ghdl_I64 (Val));
         when others =>
            raise Internal_Error;
      end case;
   end Write_Discrete;

   function Read_Uns32 (Mt : Memtyp) return Uns32 is
   begin
      pragma Assert (Mt.Typ.Sz = 4);
      return Uns32 (Read_U32 (Mt.Mem));
   end Read_Uns32;

   function Alloc_Memory (Sz : Size_Type;
                          Align2 : Natural;
                          Pool : Areapool_Acc) return Memory_Ptr
   is
      function To_Memory_Ptr is new Ada.Unchecked_Conversion
        (System.Address, Memory_Ptr);
      M : System.Address;
   begin
      Areapools.Allocate (Pool.all, M, Sz, Size_Type (2 ** Align2));
      return To_Memory_Ptr (M);
   end Alloc_Memory;

   function Alloc_Memory (Vtype : Type_Acc; Pool : Areapool_Acc)
                         return Memory_Ptr is
   begin
      return Alloc_Memory (Vtype.Sz, Natural (Vtype.Al), Pool);
   end Alloc_Memory;

   function Create_Memory (Vtype : Type_Acc) return Memtyp is
   begin
      return (Vtype, Alloc_Memory (Vtype, Current_Pool));
   end Create_Memory;

   function Create_Memory_Zero (Vtype : Type_Acc) return Memtyp
   is
      Mem : Memory_Ptr;
   begin
      Mem := Alloc_Memory (Vtype, Current_Pool);
      for I in 1 .. Vtype.Sz loop
         Write_U8 (Mem + (I - 1), 0);
      end loop;
      return (Vtype, Mem);
   end Create_Memory_Zero;

   function Create_Memory_U8 (Val : Ghdl_U8; Vtype : Type_Acc)
                             return Memtyp
   is
      pragma Assert (Vtype.Sz = 1);
      Res : Memory_Ptr;
   begin
      Res := Alloc_Memory (Vtype, Current_Pool);
      Write_U8 (Res, Val);
      return (Vtype, Res);
   end Create_Memory_U8;

   function Create_Memory_Fp64 (Val : Fp64; Vtype : Type_Acc)
                               return Memtyp
   is
      pragma Assert (Vtype.Sz = 8);
      Res : Memory_Ptr;
   begin
      Res := Alloc_Memory (Vtype, Current_Pool);
      Write_Fp64 (Res, Val);
      return (Vtype, Res);
   end Create_Memory_Fp64;

   function Create_Memory_Discrete (Val : Int64; Vtype : Type_Acc)
                                   return Memtyp
   is
      use Types_Utils;
      Res : Memory_Ptr;
   begin
      Res := Alloc_Memory (Vtype, Current_Pool);
      case Vtype.Sz is
         when 1 =>
            Write_U8 (Res, Ghdl_U8 (Val));
         when 4 =>
            Write_U32 (Res, Ghdl_U32 (To_Uns64 (Val) and 16#ffff_ffff#));
         when 8 =>
            Write_I64 (Res, Ghdl_I64 (Val));
         when others =>
            raise Internal_Error;
      end case;
      return (Vtype, Res);
   end Create_Memory_Discrete;

   function Is_Equal (L, R : Memtyp) return Boolean is
   begin
      if L = R then
         return True;
      end if;

      if L.Typ.Sz /= R.Typ.Sz then
         return False;
      end if;

      case L.Typ.Kind is
         when Type_Bit
           | Type_Logic =>
            return L.Mem (0) = R.Mem (0);
         when Type_Discrete =>
            return Read_Discrete (L.Mem, L.Typ) = Read_Discrete (R.Mem, R.Typ);
         when Type_Float =>
            return Read_Fp64 (L.Mem) = Read_Fp64 (R.Mem);
         when Type_Vector =>
            pragma Assert (L.Typ.Arr_El.Sz = 1);
            for I in 1 .. Size_Type (L.Typ.Abound.Len) loop
               if L.Mem (I - 1) /= R.Mem (I - 1) then
                  return False;
               end if;
            end loop;
            return True;
         when Type_Array =>
            declare
               Etl, Etr : Type_Acc;
               Len : Uns32;
               Off : Size_Type;
            begin
               Len := 1;
               Etl := L.Typ;
               Etr := R.Typ;
               loop
                  if Etl.Abound.Len /= Etr.Abound.Len then
                     return False;
                  end if;
                  Len := Len * Etl.Abound.Len;
                  exit when Etl.Alast;
                  Etl := Etl.Arr_El;
                  Etr := Etr.Arr_El;
               end loop;
               Etl := Etl.Arr_El;
               Etr := Etr.Arr_El;
               Off := 0;
               for I in 1 .. Len loop
                  if not Is_Equal ((Etl, L.Mem + Off),
                                   (Etr, R.Mem + Off))
                  then
                     return False;
                  end if;
                  Off := Off + Etl.Sz;
               end loop;
               return True;
            end;
         when Type_Record =>
            for I in L.Typ.Rec.E'Range loop
               declare
                  El : Rec_El_Type renames L.Typ.Rec.E (I);
               begin
                  if not Is_Equal ((El.Typ, L.Mem + El.Offs.Mem_Off),
                                   (El.Typ, R.Mem + El.Offs.Mem_Off))
                  then
                     return False;
                  end if;
               end;
            end loop;
            return True;
         when Type_Access =>
            pragma Assert (L.Typ.Sz = 4);
            return Read_U32 (L.Mem) = Read_U32 (R.Mem);
         when Type_Slice =>
            raise Internal_Error;
         when Type_Unbounded_Vector
           | Type_Unbounded_Array
           | Type_Array_Unbounded
           | Type_Unbounded_Record
           | Type_Protected
           | Type_File =>
            raise Internal_Error;
      end case;
   end Is_Equal;

   procedure Copy_Memory (Dest : Memory_Ptr; Src : Memory_Ptr; Sz : Size_Type)
   is
   begin
      for I in 1 .. Sz loop
         Dest (I - 1) := Src (I - 1);
      end loop;
   end Copy_Memory;

   function Unshare (Src : Memtyp; Pool : Areapool_Acc) return Memtyp
   is
      Res : Memory_Ptr;
   begin
      Res := Alloc_Memory (Src.Typ, Pool);
      Copy_Memory (Res, Src.Mem, Src.Typ.Sz);
      return (Src.Typ, Res);
   end Unshare;

   function Unshare (Src : Memtyp) return Memtyp
   is
      Res : Memory_Ptr;
   begin
      Res := Alloc_Memory (Src.Typ, Current_Pool);
      Copy_Memory (Res, Src.Mem, Src.Typ.Sz);
      return (Src.Typ, Res);
   end Unshare;

   function Raw_Copy (T : Type_Acc; Pool : Areapool_Acc) return Type_Acc
   is
      Addr : System.Address;
      Sz : Size_Type;
   begin
      Sz := T.all'Size / Storage_Unit;
      Allocate (Pool.all, Addr, Sz, T.all'Alignment);
      Copy_Memory (To_Memory_Ptr (Addr), To_Memory_Ptr (T.all'Address), Sz);
      return To_Type_Acc (Addr);
   end Raw_Copy;

   function Unshare (T : Type_Acc; Pool : Areapool_Acc) return Type_Acc
   is
      Res : Type_Acc;
   begin
      if T.Is_Global then
         return T;
      end if;

      Res := Raw_Copy (T, Pool);
      Res.Is_Global := True;

      case Res.Kind is
         when Type_Bit
            | Type_Logic
            | Type_Discrete
            | Type_Float =>
            null;
         when Type_Slice =>
            Res.Slice_El := Unshare (T.Slice_El, Pool);
         when Type_Array
            | Type_Array_Unbounded
            | Type_Vector =>
            Res.Arr_El := Unshare (T.Arr_El, Pool);
         when Type_Unbounded_Array
            | Type_Unbounded_Vector =>
            Res.Uarr_El := Unshare (T.Uarr_El, Pool);
            Res.Uarr_Idx := Unshare (T.Uarr_Idx, Pool);
         when Type_Record
            | Type_Unbounded_Record =>
            Res.Rec := Create_Rec_El_Array (T.Rec.Len, Pool);
            for I in T.Rec.E'Range loop
               Res.Rec.E (I) := (Offs => T.Rec.E (I).Offs,
                                 Typ => Unshare (T.Rec.E (I).Typ, Pool));
            end loop;
            if T.Rec_Base = T then
               Res.Rec_Base := Res;
            end if;
         when Type_Access =>
            if T.Acc_Acc /= null then
               Res.Acc_Acc := Unshare (T.Acc_Acc, Pool);
            else
               --  For incomplete types
               Res.Acc_Acc := null;
            end if;
         when Type_File =>
            Res.File_Typ := Unshare (T.File_Typ, Pool);
         when Type_Protected =>
            raise Internal_Error;
      end case;
      return Res;
   end Unshare;

   function Unshare_Type (Typ : Type_Acc;
                          Base : Type_Acc;
                          Global : Boolean;
                          Pool : Areapool_Acc) return Type_Acc
   is
      Res : Type_Acc;
   begin
      if Typ = Base then
         return Typ;
      end if;
      if Typ.Is_Global /= Global then
         Res := Raw_Copy (Typ, Pool);
         Res.Is_Global := Global;
      elsif Global then
         return Typ;
      else
         --  We want a local copy, but the original local type can have
         --  global sub-elements.
         Res := Typ;
      end if;

      case Res.Kind is
         when Type_Bit
           | Type_Logic
           | Type_Discrete
           | Type_Float =>
            null;
         when Type_Slice =>
            Res.Slice_El := Unshare_Type (Typ.Slice_El,
                                          Get_Array_Element (Base),
                                          Global, Pool);
         when Type_Array
           | Type_Vector =>
            Res.Arr_El := Unshare_Type (Typ.Arr_El,
                                        Get_Array_Element (Base),
                                        Global, Pool);
         when Type_Array_Unbounded
           | Type_Unbounded_Array
           | Type_Unbounded_Vector
           | Type_Unbounded_Record =>
            raise Internal_Error;
         when Type_Record =>
            if Typ /= Res then
               Res.Rec := Create_Rec_El_Array (Typ.Rec.Len, Pool);
            end if;
            for I in Typ.Rec.E'Range loop
               Res.Rec.E (I) := (Offs => Typ.Rec.E (I).Offs,
                                 Typ => Unshare_Type (Typ.Rec.E (I).Typ,
                                                      Base.Rec.E (I).Typ,
                                                      Global, Pool));
            end loop;
         when Type_Access =>
            raise Internal_Error;
         when Type_File =>
            raise Internal_Error;
         when Type_Protected =>
            raise Internal_Error;
      end case;
      return Res;
   end Unshare_Type;

   function Unshare_Type_Expr (Typ : Type_Acc; Base : Type_Acc)
                              return Type_Acc is
   begin
      return Unshare_Type (Typ, Base, False, Expr_Pool'Access);
   end Unshare_Type_Expr;

   function Unshare_Type_Instance (Typ : Type_Acc; Base : Type_Acc)
                                  return Type_Acc is
   begin
      return Unshare_Type (Typ, Base, True, Instance_Pool);
   end Unshare_Type_Instance;

   procedure Save_Type (Typ : Type_Acc;
                        Res : out Type_Acc;
                        Mem : Memory_Ptr;
                        Off : in out Size_Type;
                        Mem_Sz : Size_Type)
   is
      Sz : constant Size_Type := Typ.all'Size / Storage_Unit;
      Raw_Res : Address;
   begin
      --  Don't copy scalar types.
      case Typ.Kind is
         when Type_Bit
           | Type_Logic
           | Type_Discrete
           | Type_Float
           | Type_Access =>
            Res := Typ;
            return;
         when others =>
            null;
      end case;

      --  Copy Typ.
      Realign (Off, Typ.all'Alignment);
      pragma Assert (Off + Sz <= Mem_Sz);
      Raw_Res := To_Address (Mem + Off);
      Off := Off + Sz;
      Res := To_Type_Acc (Raw_Res);
      Copy_Memory (To_Memory_Ptr (Raw_Res),
                   To_Memory_Ptr (Typ.all'Address), Sz);
      Res.Is_Global := True;

      --  Copy elements.
      case Res.Kind is
         when Type_Bit
            | Type_Logic
            | Type_Discrete
            | Type_Float =>
            raise Internal_Error;
         when Type_Slice =>
            raise Internal_Error;
         when Type_Array
            | Type_Vector =>
            Save_Type (Typ.Arr_El, Res.Arr_El, Mem, Off, Mem_Sz);
         when Type_Unbounded_Array
            | Type_Array_Unbounded
            | Type_Unbounded_Vector
            | Type_Unbounded_Record =>
            raise Internal_Error;
         when Type_Record =>
            declare
               subtype Data_Type is Rec_El_Array (Typ.Rec.Len);
               Rec_Sz : constant Size_Type := Data_Type'Size / Storage_Unit;
            begin
               Realign (Off, Data_Type'Alignment);
               pragma Assert (Off + Rec_Sz <= Mem_Sz);
               Raw_Res := To_Address (Mem + Off);
               Off := Off + Rec_Sz;
               Copy_Memory (To_Memory_Ptr (Raw_Res),
                            To_Memory_Ptr (Typ.Rec.all'Address), Rec_Sz);
               Res.Rec := To_Rec_El_Array_Acc (Raw_Res);
               for I in Typ.Rec.E'Range loop
                  Res.Rec.E (I).Offs := Typ.Rec.E (I).Offs;
                  Save_Type (Res.Rec.E (I).Typ,
                             Typ.Rec.E (I).Typ,
                             Mem, Off, Mem_Sz);
               end loop;
            end;
         when Type_Access =>
            raise Internal_Error;
         when Type_File =>
            raise Internal_Error;
         when Type_Protected =>
            raise Internal_Error;
      end case;
   end Save_Type;

   function Save_Type (Typ : Type_Acc;
                       Mem : Memory_Ptr;
                       Mem_Sz : Size_Type) return Type_Acc
   is
      Off : Size_Type;
      Res : Type_Acc;
   begin
      Off := 0;
      Save_Type (Typ, Res, Mem, Off, Mem_Sz);
      pragma Assert (Off <= Mem_Sz);
      return Res;
   end Save_Type;

   procedure Mark_Expr_Pool (M : out Mark_Type) is
   begin
      Mark (M, Expr_Pool);
   end Mark_Expr_Pool;

   procedure Release_Expr_Pool (M : Mark_Type) is
   begin
      Release (M, Expr_Pool);
   end Release_Expr_Pool;

   function Is_Expr_Pool_Empty return Boolean is
   begin
      return Is_Empty (Expr_Pool);
   end Is_Expr_Pool_Empty;

   Bit0_Mem : constant Memory_Element := 0;
   Bit1_Mem : constant Memory_Element := 1;

   function To_Memory_Ptr is new Ada.Unchecked_Conversion
     (Address, Memory_Ptr);

   procedure Initialize is
   begin
      if Boolean_Type /= null then
         --  Restarting.  Free the global pool.
         Release (Empty_Marker, Global_Pool);
      end if;

      --  Alloc fundamental types (on the global pool).
      Current_Pool := Global_Pool'Access;
      Boolean_Type := Create_Bit_Subtype (Rng => (Left => 0,
                                                  Right => 1,
                                                  Dir => Dir_To,
                                                  Is_Signed => False));
      Logic_Type := Create_Logic_Subtype (Rng => (Left => 0,
                                                  Right => 8,
                                                  Dir => Dir_To,
                                                  Is_Signed => False));
      Bit_Type := Create_Bit_Subtype (Rng => (Left => 0,
                                              Right => 1,
                                              Dir => Dir_To,
                                              Is_Signed => False));
      Protected_Type := Create_Protected_Type;

      Boolean_Type.Is_Global := True;
      Logic_Type.Is_Global := True;
      Bit_Type.Is_Global := True;
      Protected_Type.Is_Global := True;

      Current_Pool := Expr_Pool'Access;

      Bit0 := (Bit_Type, To_Memory_Ptr (Bit0_Mem'Address));
      Bit1 := (Bit_Type, To_Memory_Ptr (Bit1_Mem'Address));
   end Initialize;

   procedure Finalize is
   begin
      pragma Assert (Boolean_Type /= null);
      Release (Empty_Marker, Global_Pool);

      Instance_Pool := null;
      Boolean_Type := null;
      Logic_Type := null;
      Bit_Type := null;
      Protected_Type := null;

      Bit0 := Null_Memtyp;
      Bit1 := Null_Memtyp;
   end Finalize;
end Elab.Vhdl_Objtypes;
