--  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, write to the Free Software
--  Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
--  MA 02110-1301, USA.

with Ada.Unchecked_Conversion;
with System; use System;
with System.Storage_Elements;

with Mutils; use Mutils;

package body Synth.Objtypes is
   function To_Bound_Array_Acc is new Ada.Unchecked_Conversion
     (System.Address, Bound_Array_Acc);

   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_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_Vector =>
            return L.Vbound = R.Vbound
              and then Are_Types_Equal (L.Vec_El, R.Vec_El);
         when Type_Unbounded_Vector =>
            return Are_Types_Equal (L.Uvec_El, R.Uvec_El);
         when Type_Slice =>
            return Are_Types_Equal (L.Slice_El, R.Slice_El);
         when Type_Array =>
            if L.Abounds.Ndim /= R.Abounds.Ndim then
               return False;
            end if;
            for I in L.Abounds.D'Range loop
               if L.Abounds.D (I) /= R.Abounds.D (I) then
                  return False;
               end if;
            end loop;
            return Are_Types_Equal (L.Arr_El, R.Arr_El);
         when Type_Unbounded_Array =>
            return L.Uarr_Ndim = R.Uarr_Ndim
              and then Are_Types_Equal (L.Uarr_El, R.Uarr_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 Discrete_Range_Width (Rng : Discrete_Range_Type) return Width
   is
      Lo, Hi : Int64;
      W : Width;
   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 := Width (Clog2 (Uns64 (Hi) + 1));
      elsif Lo = Int64'First then
         --  Handle possible overflow.
         W := 64;
      elsif Hi < 0 then
         --  Negative only.
         W := Width (Clog2 (Uns64 (-Lo))) + 1;
      else
         declare
            Wl : constant Width := Width (Clog2 (Uns64 (-Lo)));
            Wh : constant Width := Width (Clog2 (Uns64 (Hi) + 1));
         begin
            W := Width'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 Create_Bit_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,
                                                Is_Synth => True,
                                                Al => 0,
                                                Sz => 1,
                                                W => 1)));
   end Create_Bit_Type;

   function Create_Logic_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,
                                                Is_Synth => True,
                                                Al => 0,
                                                Sz => 1,
                                                W => 1)));
   end Create_Logic_Type;

   function Create_Discrete_Type (Rng : Discrete_Range_Type;
                                  Sz : Size_Type;
                                  W : Width)
                                 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);
         Al := 3;
      end if;
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Discrete,
                                                Is_Synth => True,
                                                Al => Al,
                                                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);
   begin
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Float,
                                                Is_Synth => True,
                                                Al => 3,
                                                Sz => 8,
                                                W => 64,
                                                Frange => Rng)));
   end Create_Float_Type;

   function Create_Vector_Type (Bnd : Bound_Type; 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
      return To_Type_Acc
        (Alloc (Current_Pool, (Kind => Type_Vector,
                               Is_Synth => True,
                               Al => El_Type.Al,
                               Sz => El_Type.Sz * Size_Type (Bnd.Len),
                               W => Bnd.Len,
                               Vbound => Bnd,
                               Vec_El => El_Type)));
   end Create_Vector_Type;

   function Create_Slice_Type (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,
                                  Is_Synth => El_Type.Is_Synth,
                                  Al => El_Type.Al,
                                  Sz => Size_Type (Len) * El_Type.Sz,
                                  W => Len * El_Type.W,
                                  Slice_El => El_Type)));
   end Create_Slice_Type;

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

   function Create_Bound_Array (Ndims : Dim_Type) return Bound_Array_Acc
   is
      subtype Data_Type is Bound_Array (Ndims);
      Res : Address;
   begin
      --  Manually allocate the array to handle large arrays without
      --  creating a large temporary value.
      Areapools.Allocate
        (Current_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_Bound_Array_Acc (Res);
   end Create_Bound_Array;

   function Create_Array_Type (Bnd : Bound_Array_Acc; 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);
      L : Uns32;
   begin
      L := 1;
      for I in Bnd.D'Range loop
         L := L * Bnd.D (I).Len;
      end loop;
      return To_Type_Acc (Alloc (Current_Pool,
                                 (Kind => Type_Array,
                                  Is_Synth => El_Type.Is_Synth,
                                  Al => El_Type.Al,
                                  Sz => El_Type.Sz * Size_Type (L),
                                  W => El_Type.W * L,
                                  Abounds => Bnd,
                                  Arr_El => El_Type)));
   end Create_Array_Type;

   function Create_Unbounded_Array (Ndim : Dim_Type; 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,
                                                Is_Synth => El_Type.Is_Synth,
                                                Al => El_Type.Al,
                                                Sz => 0,
                                                W => 0,
                                                Uarr_Ndim => Ndim,
                                                Uarr_El => El_Type)));
   end Create_Unbounded_Array;

   function Create_Unbounded_Vector (El_Type : 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,
                                                Is_Synth => El_Type.Is_Synth,
                                                Al => El_Type.Al,
                                                Sz => 0,
                                                W => 0,
                                                Uvec_El => El_Type)));
   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 =>
            return Arr_Type.Vec_El;
         when Type_Array =>
            return Arr_Type.Arr_El;
         when Type_Unbounded_Array =>
            return Arr_Type.Uarr_El;
         when Type_Unbounded_Vector =>
            return Arr_Type.Uvec_El;
         when others =>
            raise Internal_Error;
      end case;
   end Get_Array_Element;

   function Get_Array_Bound (Typ : Type_Acc; Dim : Dim_Type)
                            return Bound_Type is
   begin
      case Typ.Kind is
         when Type_Vector =>
            if Dim /= 1 then
               raise Internal_Error;
            end if;
            return Typ.Vbound;
         when Type_Array =>
            return Typ.Abounds.D (Dim);
         when others =>
            raise Internal_Error;
      end case;
   end Get_Array_Bound;

   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;
      else
         return Uns32 (Len);
      end if;
   end Get_Range_Length;

   function Create_Rec_El_Array (Nels : Iir_Index32) 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
        (Current_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 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;

   function Create_Record_Type (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);
      Is_Synth : Boolean;
      W : Width;
      Al : Palign_Type;
      Sz : Size_Type;
   begin
      --  Layout the record.
      Is_Synth := True;
      Al := 0;
      Sz := 0;
      W := 0;
      for I in Els.E'Range loop
         declare
            E : Rec_El_Type renames Els.E (I);
         begin
            --  For nets.
            E.Boff := W;
            Is_Synth := Is_Synth and E.Typ.Is_Synth;
            W := W + E.Typ.W;

            --  For memory.
            Al := Palign_Type'Max (Al, E.Typ.Al);
            Sz := Align (Sz, E.Typ.Al);
            E.Moff := Sz;
            Sz := Sz + E.Typ.Sz;
         end;
      end loop;
      Sz := Align (Sz, Al);

      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Record,
                                                Is_Synth => Is_Synth,
                                                Al => Al,
                                                Sz => Sz,
                                                W => W,
                                                Rec => Els)));
   end Create_Record_Type;

   function Create_Unbounded_Record (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);
   begin
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Unbounded_Record,
                                                Is_Synth => True,
                                                Al => 0,
                                                Sz => 0,
                                                W => 0,
                                                Rec => Els)));
   end Create_Unbounded_Record;

   function Create_Access_Type (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);
   begin
      return To_Type_Acc (Alloc (Current_Pool, (Kind => Type_Access,
                                                Is_Synth => False,
                                                Al => 2,
                                                Sz => 4,
                                                W => 32,
                                                Acc_Acc => Acc_Type)));
   end Create_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,
                                                Is_Synth => False,
                                                Al => 2,
                                                Sz => 4,
                                                W => 32,
                                                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,
                                                Is_Synth => False,
                                                Al => 2,
                                                Sz => 4,
                                                W => 32)));
   end Create_Protected_Type;

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

   function Get_Array_Flat_Length (Typ : Type_Acc) return Iir_Index32 is
   begin
      case Typ.Kind is
         when Type_Vector =>
            return Iir_Index32 (Typ.Vbound.Len);
         when Type_Array =>
            declare
               Len : Width;
            begin
               Len := 1;
               for I in Typ.Abounds.D'Range loop
                  Len := Len * Typ.Abounds.D (I).Len;
               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 Width is
   begin
      pragma Assert (Atype.Kind /= Type_Unbounded_Array);
      return Atype.W;
   end Get_Type_Width;

   function Get_Bound_Length (T : Type_Acc; Dim : Dim_Type) return Width is
   begin
      case T.Kind is
         when Type_Vector =>
            if Dim /= 1 then
               raise Internal_Error;
            end if;
            return T.Vbound.Len;
         when Type_Slice =>
            if Dim /= 1 then
               raise Internal_Error;
            end if;
            return T.W;
         when Type_Array =>
            return T.Abounds.D (Dim).Len;
         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, 1) = Get_Bound_Length (R, 1);
         when Type_Array =>
            for I in L.Abounds.D'Range loop
               if Get_Bound_Length (L, I) /= Get_Bound_Length (R, I) then
                  return False;
               end if;
            end loop;
            return True;
         when 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;

   --  For conversions use Address to avoidcompiler warnings about alignment.
   function To_Address is new Ada.Unchecked_Conversion
     (Memory_Ptr, Address);
   function To_Memory_Ptr is new Ada.Unchecked_Conversion
     (Address, Memory_Ptr);

   type Ghdl_U8_Ptr is access all Ghdl_U8;
   function To_U8_Ptr is
      new Ada.Unchecked_Conversion (Address, Ghdl_U8_Ptr);

   procedure Write_U8 (Mem : Memory_Ptr; Val : Ghdl_U8) is
   begin
      To_U8_Ptr (To_Address (Mem)).all := Val;
   end Write_U8;

   function Read_U8 (Mem : Memory_Ptr) return Ghdl_U8 is
   begin
      return To_U8_Ptr (To_Address (Mem)).all;
   end Read_U8;

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

   procedure Write_I32 (Mem : Memory_Ptr; Val : Ghdl_I32)
   is
      V : Ghdl_I32;
      for V'Address use To_Address (Mem);
      pragma Import (Ada, V);
   begin
      V := Val;
   end Write_I32;

   function Read_I32 (Mem : Memory_Ptr) return Ghdl_I32
   is
      V : Ghdl_I32;
      for V'Address use To_Address (Mem);
      pragma Import (Ada, V);
   begin
      return V;
   end Read_I32;

   procedure Write_U32 (Mem : Memory_Ptr; Val : Ghdl_U32)
   is
      V : Ghdl_U32;
      for V'Address use To_Address (Mem);
      pragma Import (Ada, V);
   begin
      V := Val;
   end Write_U32;

   function Read_U32 (Mem : Memory_Ptr) return Ghdl_U32
   is
      V : Ghdl_U32;
      for V'Address use To_Address (Mem);
      pragma Import (Ada, V);
   begin
      return V;
   end Read_U32;

   procedure Write_I64 (Mem : Memory_Ptr; Val : Ghdl_I64)
   is
      V : Ghdl_I64;
      for V'Address use To_Address (Mem);
      pragma Import (Ada, V);
   begin
      V := Val;
   end Write_I64;

   function Read_I64 (Mem : Memory_Ptr) return Ghdl_I64
   is
      V : Ghdl_I64;
      for V'Address use To_Address (Mem);
      pragma Import (Ada, V);
   begin
      return V;
   end Read_I64;

   procedure Write_Fp64 (Mem : Memory_Ptr; Val : Fp64)
   is
      V : Fp64;
      for V'Address use To_Address (Mem);
      pragma Import (Ada, V);
   begin
      V := Val;
   end Write_Fp64;

   function Read_Fp64 (Mem : Memory_Ptr) return Fp64
   is
      V : Fp64;
      for V'Address use To_Address (Mem);
      pragma Import (Ada, V);
   begin
      return V;
   end Read_Fp64;

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

   function "+" (Base : Memory_Ptr; Off : Size_Type) return Memory_Ptr
   is
      use System.Storage_Elements;
   begin
      return To_Memory_Ptr (To_Address (Base) + Storage_Offset (Off));
   end "+";

   function Read_Discrete (Mt : Memtyp) return Int64 is
   begin
      case Mt.Typ.Sz is
         when 1 =>
            return Int64 (Read_U8 (Mt.Mem));
         when 4 =>
            return Int64 (Read_I32 (Mt.Mem));
         when 8 =>
            return Int64 (Read_I64 (Mt.Mem));
         when others =>
            raise Internal_Error;
      end case;
   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 Alloc_Memory (Vtype : Type_Acc) return Memory_Ptr
   is
      function To_Memory_Ptr is new Ada.Unchecked_Conversion
        (System.Address, Memory_Ptr);
      M : System.Address;
   begin
      Areapools.Allocate (Current_Pool.all, M,
                          Vtype.Sz, Size_Type (2 ** Natural (Vtype.Al)));
      return To_Memory_Ptr (M);
   end Alloc_Memory;

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

   function Create_Memory_Zero (Vtype : Type_Acc) return Memtyp
   is
      Mem : Memory_Ptr;
   begin
      Mem := Alloc_Memory (Vtype);
      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);
      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);
      Write_Fp64 (Res, Val);
      return (Vtype, Res);
   end Create_Memory_Fp64;

   function Create_Memory_Discrete (Val : Int64; Vtype : Type_Acc)
                                   return Memtyp
   is
      Res : Memory_Ptr;
   begin
      Res := Alloc_Memory (Vtype);
      case Vtype.Sz is
         when 1 =>
            Write_U8 (Res, Ghdl_U8 (Val));
         when 4 =>
            Write_I32 (Res, Ghdl_I32 (Val));
         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;

      --  FIXME: not correct for records, not correct for floats!
      for I in 1 .. L.Typ.Sz loop
         if L.Mem (I - 1) /= R.Mem (I - 1) then
            return False;
         end if;
      end loop;
      return True;
   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) return Memtyp
   is
      Res : Memory_Ptr;
   begin
      Res := Alloc_Memory (Src.Typ);
      Copy_Memory (Res, Src.Mem, Src.Typ.Sz);
      return (Src.Typ, Res);
   end Unshare;

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

   procedure Init is
   begin
      Instance_Pool := Global_Pool'Access;
      Boolean_Type := Create_Bit_Type;
      Logic_Type := Create_Logic_Type;
      Bit_Type := Create_Bit_Type;

      Bit0 := (Bit_Type, To_Memory_Ptr (Bit0_Mem'Address));
      Bit1 := (Bit_Type, To_Memory_Ptr (Bit1_Mem'Address));
   end Init;
end Synth.Objtypes;
