PreviousUpNext

15.4.307  src/lib/compiler/back/low/main/nextcode/spill-nextcode-registers-g.pkg

## spill-nextcode-registers-g.pkg
#
# This file implements one of the nextcode transforms.
# For context, see the comments in
#
#     src/lib/compiler/back/top/highcode/highcode-form.api

# Compiled by:
#     src/lib/compiler/core.sublib






#
# This is a complete rewrite of the old Spill module.
# The old module suffers from some serious performance problem but
# I cannot decipher the old code fully, so instead of patching the problems up,
# I'm reimplementing it with a different algorithm.  The new code is more 
# modular, smaller when compiled, and substantially faster 
# (O (n log n) time and O (n) space).  

# As far as I can tell, the purpose of this module is to make sure the 
# number of live variables at any program point (the bandwidth) 
# does not exceed a certain limit, which is determined by the 
# size of the spill area.  

# When the bandwidth is too large, we decrease the register pressure by 
# packing live variables into spill records.  How we achieve this is
# completely different from what we did in the old code.

# First, there is something that translate_nextcode_to_treecode_g
# does  that we should be aware of:

# o  translate_nextcode_to_treecode_g
#    performs code motion!
#  
#    In particular, it will move floating point computations and
#    address computations involving only the heap pointer to 
#    their use sites (if there is only a single use).  
#    What this means is that if we have a nextcode record construction
#    statement
#  
#        RECORD (k, vl, w, e)
#  
#    we should never count the new record address w as live if w 
#    has only one use (which is often the case).
#  
#    We should do something similar to floating point, but the transformation
#    there is much more complex, so I won't deal with that.

# Secondly, there are now two new nextcode primops at our disposal:

#  1. rawrecord of Null_Or( record_kind )
#     This pure operator allocates some uninitialized storage from the heap.
#     There are two forms:
#  
#      rawrecord NULL [INT n]  allocates a tagless record of length n
#      rawrecord (THE rk) [INT n] allocates a tagged record of length n
#                                  and initializes the tag.
#  
#  2. rawupdate of cty
#       rawupdate cty (v, i, x) 
#       Assigns to x to the ith component of record v.
#       The storelist is not updated.
#  
# We use these new primops for both spilling and increment record construction.
#  
#  1. Spilling.
#     
#     This is implemented with a linear scan algorithm (but generalized
#     to trees).  The algorithm will create a single spill record at the
#     beginning of the nextcode function and use rawupdate to spill to it,
#     and SELECT or SELP to reload from it.  So both spills and reloads
#     are fine-grain operations.  In contrast, in the old algorithm 
#     "spills" have to be bundled together in records.  
#  
#     Ideally, we should sink the spill record construction to where
#     it is needed.  We can even split the spill record into multiple ones
#     at the places where they are needed.  But nextcode is not a good
#     representation for global code motion, so I'll keep it simple and
#     am not attempting this.
#  
#  2. Incremental record construction (aka record splitting).

#     Records with many values which are simulatenously live
#     (recall that single use record addresses are not considered to 
#      be live) are constructed with rawrecord and rawupdate.
#     We allot space on the heap with rawrecord first, then gradually
#     fill it in with rawupdate.  This is the technique suggested to me
#     by Matthias.
#  
#     Some restrictions on when this is applicable:
#     1. It is not a VECTOR record.  The code generator currently 
#        does not handle this case. VECTOR record uses double 
#        indirection like arrays.
#     2. All the record component values are defined in the same "basic block" 
#        as the record constructor.  This is to prevent speculative 
#        record construction. 
#
# -- Allen Leung


###                "Anyone can learn to draw, anyone can learn to play the piano,
###                 anyone can learn to write, but only a few learn it with passion
###                 and go on to inspire others."
###
###                                                 -- Shari Jones


stipulate
    package ncf =  nextcode_form;                                                       # nextcode_form         is from   src/lib/compiler/back/top/nextcode/nextcode-form.pkg
herein

    api Spill {
        #
        spill_nextcode_registers:  List(ncf::Function) -> List(ncf::Function);
    };
end;



stipulate
    package ncf =  nextcode_form;                                                       # nextcode_form         is from   src/lib/compiler/back/top/nextcode/nextcode-form.pkg
    package lv  =  highcode_codetemp;                                                   # highcode_codetemp     is from   src/lib/compiler/back/top/highcode/highcode-codetemp.pkg
    package iht =  int_hashtable;                                                       # int_hashtable         is from   src/lib/src/int-hashtable.pkg

    debug          = FALSE;
    max_bandwidth  = 100;               # Kick in spilling when this many values 
                                        # are live at the same time.

    split_large_records = TRUE;         # TRUE to enable record splitting.
    max_record_length   = 16;           # Split record of this size or larger.
herein

    # We are invoked (only) from:
    #
    #    src/lib/compiler/back/top/main/backend-tophalf-g.pkg
                                                                                        # Machine_Properties    is from   src/lib/compiler/back/low/main/main/machine-properties.api
    generic package   spill_nextcode_registers_g   (
        #             ==========================
        #
        mp:  Machine_Properties                                                         # Typically                       src/lib/compiler/back/low/main/intel32/machine-properties-intel32.pkg
    ) 

    : (weak) Spill                                                                      # Spill                 is from   src/lib/compiler/back/low/main/nextcode/spill-nextcode-registers-g.pkg

    {

        debug_nextcode_spill      =  global_controls::lowhalf::make_bool ("debug_nextcode_spill",      "Nextcode spill debug mode");
        debug_nextcode_spill_info =  global_controls::lowhalf::make_bool ("debug_nextcode_spill_info", "Nextcode spill info debug mode");

        infix my 70  \/ ; 
        infix my 80  /\ ;
#       infix my 60  -- ;

        error = error_message::impossible;
        pr    = global_controls::print::say;
        i2s   = int::to_string;

        maxgpfree
            = 
            int::min (mp::spill_area_size / (2 * mp::value_size), max_bandwidth);

        maxfpfree
            = 
            int::min (mp::spill_area_size / (2 * mp::float_size_in_bytes), max_bandwidth);

        # Prettyprinting:
        #
        fun dump (title, nextcode_fun)
            =
            if *debug_nextcode_spill
                 pr ("------------ " + title + " the spill phase ---------- \n");
                 prettyprint_nextcode::print_nextcode_function  nextcode_fun;
                 pr "--------------------------------------\n\n";
            fi;


        # The following data package groups
        # together type specific functions.
        #
        Type_Info
            = 
            TYPE_INFO  
            { max_live:     Int,                                # Max live values allowed.
              is_variable:  ncf::Codetemp -> Bool,              # Is variable a candidate for spilling? 
              item_size:    Int                                 # Number of words per item.
            };

        Spill_Candidate
            =
            SPILL_CANDIDATE 
              { highcode_variable:  ncf::Codetemp,
                cty:                ncf::Type,
                rank:               Int                         # Distance to next use.
              };

        # Cheap set representation:
        #
        package simple_set {

                package set = int_red_black_set; 

                my (\/) = set::union;
                my (/\) = set::intersection;
#               my (--) = set::difference;

                ooo     = set::empty;       
                card    = set::vals_count;                      #  Cardinality 

                fun rmv (s, x)
                    =
                    set::drop (s, x);
        };

        # Spill candidates set representation;
        # this one has to be ranked:
        #
        package ranked_set {

                package set
                    =
                    red_black_set_g (

                        Key = Spill_Candidate;

                        fun compare (SPILL_CANDIDATE { rank=>r1, highcode_variable=>v1, ... },
                                     SPILL_CANDIDATE { rank=>r2, highcode_variable=>v2, ... } )
                            = 
                            case (int::compare (r1, r2))
                                EQUAL => int::compare (v1, v2);
                                ord   => ord;
                            esac;
                    );

                exception ITEM  set::Item;

                # As priority queue 
                #
                fun next s
                    = 
                    set::fold_backward
                        (\\ (x, _) =  raise exception ITEM x)
                        NULL
                        s 
                    except
                        ITEM x =  THE (x, set::drop (s, x));

                # Abbreviations for set operations:
                #
                my (\/) = set::union;
                my (/\) = set::intersection;
#               my (--) = set::difference;

                ooo     = set::empty;       
                card  = set::vals_count;     #  Cardinality 

                fun rmv (s, x)
                    =
                    set::drop (s, x);
        };

        fun rk_to_ncftype  ncf::rk::FLOAT64_FATE_FN     =>   ncf::typ::FLOAT64;
            rk_to_ncftype  ncf::rk::FLOAT64_BLOCK       =>   ncf::typ::FLOAT64;
            rk_to_ncftype _                             =>   ncf::bogus_pointer_type;
        end;

        fun splittable ncf::rk::VECTOR =>   FALSE;      # Not supported in backend (yet)  XXX BUGGO FIXME
            splittable _               =>   TRUE;
        end;

        ###########################################################################
        #
        # All nextcode functions can be independently processed.
        #
        # Some complexity assumptions: 
        #   Hashing is O (1)
        #   N = max { number of lvars, size of nextcode function }
        #
        ###########################################################################

        ###########################################################################
        # markFpAndRec
        # =============
        # Mark all floating point variables and return a hashtable
        # 
        # This is needed because we do spilling of integer and floating
        # point stuff separately.
        #
        # This function takes O (N) time and space
        ###########################################################################

        fun mark_fp_and_rec  (nextcode_fun:  ncf::Function)
            = 
            {   nextcode_fun ->   (callers_info, f, args, arg_types, body);

                include package   simple_set;

                exception FLOAT_SET;

                float_set
                   =
                   iht::make_hashtable  { size_hint => 32,  not_found_exception => FLOAT_SET };

                add_to_float_set
                    =
                    iht::set float_set;

                fun fp (r, ncf::typ::FLOAT64) =>   add_to_float_set (r, TRUE);
                    fp (r, _)                 =>   ();
                end;

                exception RECORD_SET;

                record_set =   iht::make_hashtable  { size_hint => 32,  not_found_exception => RECORD_SET };

                markrec   =   iht::set record_set;
                findrec   =   iht::find   record_set;

                #  Mark all record uses:

                rec_uses
                    = 
                    apply
                        \\ (ncf::CODETEMP v, _)
                            => 
                            case (findrec v)   
                                #
                                THE n =>  markrec (v, n+1);
                                NULL  =>  ();                   # Not a record address.
                            esac;

                            _ => ();
                        end;


                fun mark_pure (p, w)
                    =
                    case p
                        #                      
                        # These "pure" operators actually allot storage! 
                        #
                        ( ncf::p::WRAP_FLOAT64 | ncf::p::IWRAP | ncf::p::WRAP_INT1 | ncf::p::MAKE_ZERO_LENGTH_VECTOR
                        | ncf::p::MAKE_REFCELL | ncf::p::MAKE_WEAK_POINTER_OR_SUSPENSION | ncf::p::ALLOT_RAW_RECORD _
                        )
                            =>
                            markrec (w, 0); 

                        _ => ();
                    esac;

                fun markfp e
                    = 
                    case e
                        #
                        ncf::TAIL_CALL _                                        =>   ();
                        ncf::JUMPTABLE r                                        =>   apply  markfp  r.nexts; 
                        #
                        ncf::GET_FIELD_I            { type,   to_temp, next, ... } =>   { fp (to_temp, type);                           markfp   next; };
                        ncf::GET_ADDRESS_OF_FIELD_I {                  next, ... } =>                                                   markfp   next;
                        #
                        ncf::DEFINE_RECORD          { fields, to_temp, next, ... } =>   { rec_uses fields;   markrec (to_temp, 0);      markfp   next; };
                        #
                        ncf::STORE_TO_RAM           {                 next, ... } =>                                                    markfp   next;
                        ncf::FETCH_FROM_RAM         {  to_temp, type, next, ... } =>   { fp (to_temp, type);                            markfp   next; };
                        #
                        ncf::ARITH                  {  to_temp, type, next, ... } =>   { fp (to_temp, type);                            markfp   next; };
                        #
                        ncf::PURE                { op, to_temp, type, next, ... } =>   { mark_pure (op, to_temp); fp (to_temp, type);   markfp   next; };

                        ncf::RAW_C_CALL                  { to_ttemps, next, ... } =>   { apply fp to_ttemps;                            markfp   next; };
                        #
                        ncf::IF_THEN_ELSE { then_next, else_next, ... }
                            =>
                            {   markfp  then_next;
                                markfp  else_next;
                            };

                        ncf::DEFINE_FUNS _ =>   error "ncf::DEFINE_FUNS in Spill::markfp";
                    esac;

                paired_lists::apply fp (args, arg_types);               # Mark function parameters.
                markfp body;                                    # Mark function body.


                # Filter out multiple uses of record values because these
                # are not forward propagated by the backend.

                if debug

                    iht::keyed_apply
                        (\\ (v, n)
                            =
                            if (n >= 2)
                                pr (lv::name_of_highcode_codetemp v + " uses=" + i2s n + "\n");
                            fi
                        )
                        record_set;
                fi;

                iht::filter
                    (\\ n =   n <= 1)
                    record_set;

                (float_set, record_set);
            };

        ###########################################################################
        # needsSpilling
        # =============
        # This function checks whether we need to perform spilling for 
        # the current type, which is either gpr or fpr. 
        # Parameterized by type info.  This is supposed to be a cheap check
        # since most of the time this function should return FALSE,
        # so no information is saved.
        #
        # This function takes O (N log N) time and O (N) space.
        ###########################################################################

        fun needs_spilling   (TYPE_INFO { max_live, is_variable, ... } )   (nextcode_fun:  ncf::Function)
            = 
            {   nextcode_fun ->   (callers_info, f, args, arg_types, body);

                include package   simple_set;

                exception TOO_MANY;

                bandwidth =   REF 0;

                # Make sure |s| is not too large. 
                # Note: card is a O (1) operation.

                fun check s
                    = 
                    {   n =   card s;

                        if (n > *bandwidth)
                            bandwidth := n;
                        fi;

                        if (n >= max_live)  raise exception TOO_MANY;
                        else                s;
                        fi;
                    };

                # This function inserts lvars of
                # the current type into set s:
                #
                fun uses (vs, s)
                    = 
                    f (vs, s)
                    where
                        fun f ((ncf::CODETEMP x) ! vs, s)
                                => 
                                f ( vs,
                                    is_variable x  ??  set::add (s, x)
                                                   ::  s
                                  );

                            f (_ ! vs, s)
                                =>
                                f (vs, s);

                            f ([], s)
                                =>
                                check s;
                        end;
                    end;

                # Remove w (a definition) from s.  
                #
                fun def (w, s)
                    =
                    rmv (s, w);

                # Union of a list of sets S_1, ..., S_n.
                #
                # Runs in O (m \log m) time and space 
                # where m = \sum_{ i=1\ldots n } |S_i|
                #
                unions
                    =
                    list::fold_backward   (\/)   ooo;


                # Compute the set of free vars at each program point.
                # Raise exception TOO_MANY if the live set exceeds maxLive.
                # This phase runs in total O (N log N) time and O (N) space.
                #
                fun freevars e
                    =
                    case e
                        #
                        ncf::TAIL_CALL              { fn, args }                => uses (fn ! args, ooo);
                        ncf::JUMPTABLE              { i, nexts, ... }           => uses ([i], unions (map freevars nexts));
                        #
                        ncf::GET_FIELD_I            { record, to_temp, next, ... } =>  uses([record], def (to_temp, freevars next));
                        ncf::GET_ADDRESS_OF_FIELD_I { record, to_temp, next, ... } =>  uses([record], def (to_temp, freevars next));
                        #
                        ncf::DEFINE_RECORD          { fields, to_temp, next, ... } =>  uses ((map #1 fields), def (to_temp, freevars next));
                        #
                        ncf::STORE_TO_RAM           {            args, next, ... } =>  uses (args,               freevars next );
                        ncf::FETCH_FROM_RAM         {   to_temp, args, next, ... } =>  uses (args, def (to_temp, freevars next));
                        #
                        ncf::ARITH                  {   to_temp, args, next, ... } =>  uses (args, def (to_temp, freevars next));
                        ncf::PURE                   {   to_temp, args, next, ... } =>  uses (args, def (to_temp, freevars next));

                        ncf::RAW_C_CALL             { to_ttemps, args, next, ... }
                            =>
                            uses ( args,

                                   fold_forward
                                       (\\((w, _), s) = def (w, s))
                                       (freevars next)
                                       to_ttemps
                                 );

                        ncf::IF_THEN_ELSE { args, then_next, else_next, ... }
                            =>
                            uses (args, freevars then_next \/ freevars else_next);

                        ncf::DEFINE_FUNS _ => error "ncf::DEFINE_FUNS in Spill::freevars";
                   esac;

                needs_spilling
                    =
                    {   freevars body;
                        FALSE;
                    }
                    except
                        TOO_MANY = TRUE;

                { needs_spilling,
                  bandwidth     => *bandwidth
                };

            };                  #  needsSpilling 

        ############################################################################
        # linearScan
        # ==========
        #
        # Perform the actual spilling.
        #
        # The algorithm is derived from linear-scan RA algorithms. 
        # But since we are dealing with trees, (and because of immutable
        # data structures), we'll do this in multiple passes rather than
        # a single pass.
        #
        # What spilling means in nextcode is transforming:
        #    
        #
        #   v <- f(...)  # Definition
        #   ....
        #   ... <- g(... v ...)  # use
        #
        # into:
        #
        #   spilled <- rawrecord NULL m    # Create an uninitialized spill record of length m
        #   ....
        #   v <- f(...) # Definition
        #   rawupdate (spilled, v_offset, v) 
        #   ...
        #   ... <- g(... SELP (spilled, v_offset) ...)    # reload
        #
        # Important notes:
        #  1. The spill record is never live beyond the 
        #     nextcode function, so we never even have to assign
        #     its record tag.  
        #
        #  2. We spill all tagged/untagged values into a spill record,
        #     without segregating them by their types, so we are mixing 
        #     32-bit integers, 31-bit tagged ints, and pointers together.  
        #     This is safe because of (1).
        #
        # This function takes a total of O (N log N) time and O (N) space. 
        ###########################################################################

        fun linear_scan   (TYPE_INFO { max_live, is_variable, item_size, ... } )   (nextcode_fun:  ncf::Function)
            = 
            {   nextcode_fun ->   (callers_info, f, args, arg_types, body);

                include package   ranked_set;

                dump("before", nextcode_fun);

                # Information about each highcode_variable 
                #
                Lvar_Info = LVAR_INFO { use_count:   Ref( Int ),                        # Number of uses in this function.
                                        def_point:   Int,                               # Level of definition.
                                        def_block:   Int,                               # Block of definition.
                                        cty:         ncf::Type,
                                        nearest_use: Ref( Int )                 # min { level (x) | x in uses (v) } 
                                      }; 

                exception LVAR_INFO_EXCEPTION;

                if *debug_nextcode_spill_info
                    pr "Nextcode Spill: linearScan\n";
                fi;

                lvar_info   =   iht::make_hashtable  { size_hint => 32,  not_found_exception => LVAR_INFO_EXCEPTION };
                lookup_lvar =   iht::get  lvar_info;

                fun spill_cand v
                    = 
                    {   (lookup_lvar v)
                            ->
                            LVAR_INFO { nearest_use, use_count, def_point, cty, ... };

                        dist =   *nearest_use - def_point;

                        rank =   dist;                  #  for now 

                        SPILL_CANDIDATE { highcode_variable=>v, cty, rank };
                    };

                # ----------------------------------------------------------------------
                # Gather information about each highcode_variable
                # We partition the nextcode function into blocks.  
                #     A block is a continuous group of statements without
                #     controlflow or store updates. 
                # This phase runs in O (N) time and space.
                # ---------------------------------------------------------------------
                stipulate 
                    #
                    infinity   =   10000000;                                    # For sufficiently large values of 10000000. :-)
                    enter_lvar =   iht::set lvar_info;

                    fun def (v, t, b, n)
                        = 
                        enter_lvar (v, LVAR_INFO { use_count => REF 0, 
                                                   def_point => n,
                                                   def_block => b,
                                                   cty => t,
                                                   nearest_use =>REF infinity
                                                 }
                                   );

                    fun use (ncf::CODETEMP v, n)
                            => 
                            if (is_variable v)

                                my  LVAR_INFO { use_count, nearest_use, ... }
                                    =
                                    lookup_lvar v; 

                                use_count :=   *use_count + 1;

                                nearest_use :=   int::min(*nearest_use, n);
                            fi;

                        use _
                            =>
                            ();
                    end;

                    fun uses ([],     n) =>  ();
                        uses (v ! vs, n) =>  { use(v, n); uses (vs, n);};
                    end;

                    fun gather (e, b, n)
                        =
                        {   fun gathers ([], b, n)
                                    =>
                                    ();

                                gathers (e ! es, b, n)
                                    =>
                                    {   gather (e, b, n);
                                        gathers (es, b, n);
                                    };
                            end;

                            fun f0 (vl, e)
                                =
                                {   uses (vl, n);

                                    gather (e, b+1, n+1);
                                };

                            fun f1 (v, w, t, e)
                                =
                                {   use(v, n);
                                    def (w, t, b, n);
                                    gather (e, b, n+1);
                                };

                            fun fx (vl, w, t, e, b)
                                =
                                {   uses (vl, n);
                                    def (w, t, b, n);
                                    gather (e, b, n+1);
                                };

                            case e
                                ncf::TAIL_CALL              { fn, args }                        =>   uses (fn ! args, n);
                                ncf::JUMPTABLE              { i, nexts, ... }                   =>   { use(i, n);   gathers (nexts, b+1, n+1); };
                                #
                                ncf::GET_FIELD_I            { record, to_temp, type, next, ... }        =>   f1 (record, to_temp, type,                    next);
                                ncf::GET_ADDRESS_OF_FIELD_I { record, to_temp,       next, ... }        =>   f1 (record, to_temp, ncf::bogus_pointer_type, next);
                                #
                                ncf::DEFINE_RECORD          { fields, to_temp,       next, ... }        =>   fx (map #1 fields, to_temp, ncf::bogus_pointer_type, next, b);
                                #
                                ncf::STORE_TO_RAM           { args,                  next, ... }        =>   f0 (args, next);
                                ncf::FETCH_FROM_RAM         { args, to_temp, type,   next, ... }        =>   fx (args, to_temp, type, next, b);
                                #
                                ncf::ARITH                  { args, to_temp, type,   next, ... }        =>   fx (args, to_temp, type, next, b);
                                ncf::PURE                   { args, to_temp, type,   next, ... }        =>   fx (args, to_temp, type, next, b);
                                #
                                ncf::RAW_C_CALL { args, to_ttemps, next, ... }
                                    =>
                                    {   b = b+1;

                                        uses (args, n);

                                        apply (\\ (w, t) =  def (w, t, b, n)) 
                                              to_ttemps;

                                        gather (next, b, n+1);
                                    };

                                ncf::IF_THEN_ELSE { args, then_next, else_next, ... }
                                    =>
                                    {   uses (args, n);
                                        gathers( [then_next, else_next], b+1, n+1 );
                                    };

                                ncf::DEFINE_FUNS _
                                    =>
                                    error "ncf::DEFINE_FUNS in Spill::gather";
                            esac;
                        };

                herein

                    # Always remember to define the arguments! 
                    #
                    my () =  paired_lists::apply  (\\ (v, t) = def (v, t, 0, 0))  (args, arg_types);
                    my () =  gather (body, 1, 1);

                end;                    # Gather 

                my () = if *debug_nextcode_spill  pr "Nextcode Spill: gather done\n"; fi;

                # -----------------------------------------------------------------
                # 
                # Spill tables and utilities
                #
                # -----------------------------------------------------------------

                exception SPILL_TABLE;

                spill_table
                    =
                    iht::make_hashtable  { size_hint => 32,  not_found_exception => SPILL_TABLE } :
                             iht::Hashtable ((ncf::Value, Int, ncf::Type)); 
                            #
                            #  Variable -> spillRecord * spill offset * cty 

                enter_spill  = iht::set spill_table;   
                find_spill   = iht::find spill_table;
                is_spilled   = iht::contains_key spill_table;

                current_spill_record = REF (NULL:   Null_Or( (ncf::Codetemp, ncf::Value) ));


                # Generate a new spill record variable:

                fun gen_spill_rec ()
                    = 
                    case *current_spill_record
                      
                         THE x
                             =>
                             x;

                         NULL
                             => 
                             {   v =   lv::issue_named_highcode_codetemp
                                           (symbol::make_value_symbol "spillrec");

                                 e =   ncf::CODETEMP v;

                                 current_spill_record
                                     :=
                                     THE (v, e);

                                 (v, e);
                             };
                    esac;


                # This function finds up to m good
                # spill candidates from the live set:

                fun find_good_spills (0, lll, sp_off)
                       =>
                       (lll, sp_off);

                    find_good_spills (m, lll, sp_off)
                        =>
                        case (next lll)
                          
                            NULL => (lll, sp_off);                     #  no more spill candidates! 

                            THE (SPILL_CANDIDATE { highcode_variable, cty, rank, ... }, lll)
                                =>
                                {   offset =   sp_off;                          # Should align when we have 64-bit values. XXX BUGGO FIXME

                                    my (_, sp_rec_expression)
                                       =
                                       gen_spill_rec ();

                                    enter_spill (highcode_variable, (sp_rec_expression, offset, cty));

                                    fun inc (sp_off, cty)
                                        =
                                        sp_off + 1;              # Should get at cty 
                                                                    # when we have 64-bit values  XXX BUGGO FIXME

                                    # OK: It is actually live and
                                    # has not been spilled:
                                    #
                                    if *debug_nextcode_spill
                                         pr("Spilling " + lv::name_of_highcode_codetemp highcode_variable + " rank=" + i2s rank + "\n");
                                    fi;

                                    find_good_spills (m - 1, lll, inc (sp_off, cty));
                                };
                        esac;
                end;


                # Can and should the record be split?  
                # Split if,
                #  1. we can handle the record type
                #  2. if it has >= max_record_length live lvars as arguments
                #  3. All its arguments are defined in the same block as the record.
                #
                fun should_split_record (rk, vl, b)
                    = 
                    split_large_records
                    and
                    splittable rk and f (vl, 0)
                    where
                        fun ok_path (ncf::VIA_SLOT (i, p)) =>   ok_path p;
                            ok_path (ncf::SLOT 0)      =>   TRUE;
                            ok_path _                 =>   FALSE;
                        end;

                        fun f ([], n)
                                =>
                                n >= max_record_length; 

                            f((ncf::CODETEMP v, p) ! vl, n)
                                =>
                                {   my  LVAR_INFO { def_block, ... }
                                        =
                                        lookup_lvar v;

                                    def_block == b and
                                    ok_path p      and

                                    if (is_variable v and not (is_spilled v))   f (vl, n+1);
                                    else                                        f (vl, n  );
                                    fi;

                                };

                            f ((_, ncf::SLOT 0) ! vl, n)
                                =>
                                f (vl, n);

                            f _
                                =>
                                FALSE;
                        end;
                    end;


                # Tables for splitting a record 

                exception RECORD_TABLE;

                Split_Record_Item 
                    =
                    SPLIT_RECORD_ITEM 
                     { record:    ncf::Codetemp,
                       kind:      ncf::Record_Kind,
                       len:       Int,
                       offset:    Int,
                       path:      ncf::Fieldpath,
                       num_vars:  Ref( Int ),
                       consts:    List( (Int, ncf::Value) )
                     };

                record_alloc_table =   iht::make_hashtable  { size_hint => 16,  not_found_exception => RECORD_TABLE };
                split_record_table =   iht::make_hashtable  { size_hint => 16,  not_found_exception => RECORD_TABLE };

                find_record_item   =   iht::find   record_alloc_table;
                enter_record_item  =   iht::set record_alloc_table;
                mark_split_record  =   iht::set split_record_table;

                fun insert_record_item (v, x)
                    =
                    enter_record_item (v, x ! the_else (find_record_item v,[]));


                # Mark record w as being split.  
                # Enter the appropriate info to all its arguments.

                fun split_record_construction (rk, vl, w)
                    =
                    {   fun f (i, (ncf::CODETEMP v, offp) ! vl, vars, consts)
                                => 
                                f (i+1, vl, (i, v, offp) ! vars, consts);

                            f (i, (c, ncf::SLOT 0) ! vl, vars, consts)
                                => 
                                f (i+1, vl, vars, (i, c) ! consts);

                            f (_, [], vars, consts)
                                =>
                                (vars, consts);

                            f _ =>
                                error "Nextcode Spill::split_record_construction";
                        end;

                        my  (vars, consts)
                            =
                            f (0, vl, [], []);

                        n =  length vars;

                        if (n == 0)
                            error "Nextcode Spill: splitting constant record";
                        fi;

                        if *debug_nextcode_spill_info
                             pr("Splitting record " + lv::name_of_highcode_codetemp w + " len=" + i2s n + "\n");
                        fi;

                        len     =   length vl;
                        num_vars =   REF n;

                        fun enter (i, v, path)
                            =
                            {   item = SPLIT_RECORD_ITEM {

                                           record  => w,
                                           kind    => rk,
                                           len,
                                           offset  => i, 
                                           path, 
                                           num_vars,
                                           consts
                                       };

                               insert_record_item (v, item);
                            };

                        apply enter vars;
                        mark_split_record (w, TRUE);
                    };

                # -----------------------------------------------------------------
                #  Linear scan spilling.
                #  This function marks all spill/reload sites.
                # 
                #  Parameters:
                #   e     --- nextcode expression
                #   b     --- current block
                #   spOff --- current available spill offset
                # 
                #  Return:
                #   live      --- the set of live lvars in e 
                #   spills --- the number of spills
                #    
                #  This phase takes O (N log N) time and O (N) space
                # -----------------------------------------------------------------
                fun scan (e, b, sp_off)
                    = 
                    {   # Add uses to live set:
                        #
                        fun add_uses ([], live)
                                =>
                                live;

                            add_uses (ncf::CODETEMP v ! vs, live)
                                =>
                                add_uses (

                                    vs,

                                    if   (is_variable v and not (is_spilled v))
                                         set::add (live, spill_cand v);
                                    else live;fi
                                );

                            add_uses(_ ! vs, live)
                                =>
                                add_uses (vs, live);
                        end;

                        #  This function kills a definition 
                        #
                        fun kill (w, live)
                            =
                            is_variable w  ??  rmv (live, spill_cand w)
                                           ::  live;

                        # This function finds
                        # things to spill: 
                        #
                        fun gen_spills (live, sp_off)
                            = 
                            {   to_spills =   card live - max_live;

                                if (to_spills > 0)  find_good_spills (to_spills, live, sp_off);
                                else                (live, sp_off);
                                fi;
                            };

                        #  This function visits a list of fates and
                        #  gathers up the info 

                        fun scan_list es
                            = 
                            f es
                            where
                                b =   b + 1;

                                fun f [] => (ooo, 0);
                                    f [e] => scan (e, b, sp_off);

                                    f (e ! es)
                                        => 
                                        {   my (lll1, sp_off1) =   scan (e, b, sp_off);
                                            my (lll2, sp_off2) =   f es;

                                            (lll1 \/ lll2, int::max (sp_off1, sp_off2));
                                        };
                                end;
                            end;

                        # This function scans normal nextcode operators 
                        # with one definition and one fate
                        # 
                        #   w:  t <- f vs; e
                        #
                        fun scan_op (vs, w, e, b)
                            =
                            {   my (lll, sp_off) =   scan (e, b, sp_off);       # Do fate.
                                lll          =   kill (w, lll);                 # Remove definition.
                                lll          = add_uses (vs, lll);              # Add uses.
                                my (lll, sp_off) = gen_spills (lll, sp_off);    # Find spill.
                                (lll, sp_off);
                            };

                        # This function scans statements
                        # with multiple fates:
                        #
                        fun scan_statement (vs, es)
                            =
                            {   my (lll, sp_off) =   scan_list es;              # Do fate.
                                lll          =   add_uses (vs, lll);            # Add uses.
                                my (lll, sp_off) =   gen_spills (lll, sp_off);  # Find spills.
                                (lll, sp_off);
                            };

                        # This function scans
                        # record constructors:
                        #
                        fun scan_rec (rk, vl, w, e)
                            = 
                            {   my  (lll, sp_off)
                                    =
                                    scan (e, b, sp_off);                #  Do fate 

                                my  (lll, sp_off)
                                    =
                                    if (should_split_record (rk, vl, b))

                                        split_record_construction (rk, vl, w);
                                        (lll, sp_off);
                                    else
                                        lll =   kill (w, lll);
                                        lll =   add_uses (map #1 vl, lll);
                                        gen_spills (lll, sp_off);
                                    fi;

                                (lll, sp_off);
                            };

                        my (lll, num_spills)
                           = 
                           case e
                               ncf::TAIL_CALL               { fn, args }                =>  scan_statement (fn ! args, []);
                               ncf::JUMPTABLE               { i, nexts, ... }           =>  scan_statement([i], nexts);
                               #        
                               ncf::GET_FIELD_I             { record, to_temp, next, ... } =>  scan_op([record], to_temp, next, b);
                               ncf::GET_ADDRESS_OF_FIELD_I  { record, to_temp, next, ... } =>  scan_op([record], to_temp, next, b);
                               #        
                               ncf::DEFINE_RECORD           { kind, fields, to_temp, next }=>  scan_rec (kind, fields, to_temp, next);
                               #        
                               ncf::STORE_TO_RAM            { args,          next, ... }        =>  scan_statement (args, [next]);
                               ncf::FETCH_FROM_RAM          { args, to_temp, next, ... }        =>  scan_op (args, to_temp, next, b);
                               #        
                               ncf::ARITH                   { args, to_temp, next, ... }        =>  scan_op (args, to_temp, next, b);
                               ncf::PURE                    { args, to_temp, next, ... }        =>  scan_op (args, to_temp, next, b);

                               ncf::RAW_C_CALL { args, to_ttemps, next, ... }
                                   =>
                                   {   b =   b+1;

                                       (scan (next, b, sp_off))
                                           ->
                                           (lll, sp_off);

                                       lll =   fold_forward
                                                   (\\ ((w, _), lll) =  kill (w, lll))
                                                   lll
                                                   to_ttemps;

                                       lll =   add_uses (args, lll);

                                       (gen_spills (lll, sp_off))
                                           ->
                                           (lll, sp_off);
                                           

                                       (lll, sp_off);
                                   };

                               ncf::IF_THEN_ELSE r
                                   =>
                                   scan_statement (r.args, [r.then_next, r.else_next]);

                               ncf::DEFINE_FUNS _ =>   error "ncf::DEFINE_FUNS in Spill::scan";
                           esac;

                        (lll, num_spills);
                    };

                # Scan the body 
                #
                my (lll, num_spills)
                    =
                    scan (body, 1, 0);


                if *debug_nextcode_spill
                    pr("Nextcode Spill: scan done. Spilling " + i2s num_spills + "\n");
                fi;


                #  Generate reloads for a list of arguments.
                #  Returns:
                #     the rewritten list of arguments
                #     a function for inserting selects.
                #
                fun put_reloads vs
                    =
                    g (vs, [], \\ e = e)
                    where
                        fun g ([], vs', f)
                                =>
                                (reverse vs', f);

                            g ((v as ncf::CODETEMP x) ! vs, vs', f)
                                =>
                                case (find_spill x)
                                    #
                                    NULL => g (vs, v ! vs', f);
                                    #   
                                    THE (spill_rec, off, cty)
                                        =>
                                        {   x'   = lv::clone_highcode_codetemp x;
                                            v'   = ncf::CODETEMP x';

                                            fun f' next
                                                =
                                                ncf::GET_FIELD_I  { i      =>  off,
                                                                    record =>  spill_rec,
                                                                    to_temp   =>  x',
                                                                    type   =>  cty,
                                                                    next   =>  f next
                                                                  };

                                            g (vs, v' ! vs', f'); 
                                        };
                                esac;

                            g (v ! vs, vs', f)
                                =>
                                g (vs, v ! vs', f);
                        end;
                    end;


                #  Generate reloads for record paths
                #  Returns:
                #     the rewritten list of record paths
                #
                fun put_path_reloads vl
                    =
                    f (vl, [])
                    where
                        fun f ([], vl')
                                =>
                                reverse vl';

                            f((v as ncf::CODETEMP x, p) ! vl, vl')
                                =>
                                case (find_spill x)
                                    #
                                    NULL
                                        =>
                                        f (vl, (v, p) ! vl');
                                    #   
                                    THE (spill_rec, off, cty)
                                        => 
                                        f (vl, (spill_rec, ncf::VIA_SLOT (off, p)) ! vl');
                                esac;

                            f (v ! vl, vl')
                                =>
                                f (vl, v ! vl');
                        end;
                    end;

                # This function generate
                # spill code for variable w 
                #
                fun put_spill (w, next)
                    = 
                    case (find_spill w)
                        #
                        THE (spill_record, off, cty)
                            => 
                            ncf::STORE_TO_RAM { op   =>  ncf::p::SET_NONHEAP_RAMSLOT cty,
                                                args =>  [ spill_record, ncf::INT off, ncf::CODETEMP w ],
                                                next
                                              };

                        NULL => next;
                    esac;


                # Emit spill record code
                #
                fun create_spill_record (0, next)
                        =>
                        next;

                    create_spill_record (num_spills, next)
                        => 
                        {   (gen_spill_rec ())
                                ->
                                (spill_rec_lvar, _);
                                

                            m =   num_spills * item_size;

                            next = ncf::PURE  { op   =>  ncf::p::ALLOT_RAW_RECORD NULL,
                                                args =>  [ncf::INT m],
                                                to_temp =>  spill_rec_lvar,
                                                type =>  ncf::bogus_pointer_type,
                                                next
                                              };

                            current_spill_record := NULL;               #  Clear 

                            next;
                        };
                end;

                record_is_split       =   iht::contains_key split_record_table;
                find_split_record_arg =   iht::find record_alloc_table;


                # Proj (v, path, e) ==> w <- v::path ; e[w/v]
                #
                fun proj (v, ncf::SLOT 0, e) =>   e v;

                    proj (v, ncf::VIA_SLOT (i, p), e)
                        =>
                        {   v'   =   lv::issue_highcode_codetemp ();
                            next =   e v';

                            ncf::GET_FIELD_I { i, record => ncf::CODETEMP v, to_temp => v', type => ncf::bogus_pointer_type, next };
                        };

                    proj _ =>   error "spill_g: proj";
                end;


                # Generate
                #     record::offset <- v::path ; e
                #
                fun init_record_item (record, rk, offset, v, path, next)
                    = 
                    proj
                      ( v,
                        path, 
                        \\ x =  ncf::STORE_TO_RAM
                                  {
                                    op   =>  ncf::p::SET_NONHEAP_RAMSLOT (rk_to_ncftype rk),
                                    args =>  [ ncf::CODETEMP record,  ncf::INT offset,  ncf::CODETEMP x ],
                                    next
                                  }
                      );


                # Generate code to create a record.
                #
                fun create_record (record, rk, len, consts, next)
                    =
                    {   next =   put_spill (record, next);

                        op =   ncf::p::SET_NONHEAP_RAMSLOT (rk_to_ncftype rk);

                        fun init ((i, c), next)
                            =
                            ncf::STORE_TO_RAM
                              { op,
                                args =>  [ncf::CODETEMP record, ncf::INT i, c],
                                next
                              };

                        next =   fold_backward  init  next  consts;

                        next =  ncf::PURE { op   =>  ncf::p::ALLOT_RAW_RECORD (THE rk),
                                            args =>  [ncf::INT len],
                                            to_temp =>  record,
                                            type =>  ncf::bogus_pointer_type,
                                            next
                                          };

                        next;
                    };


                # It is the definition of highcode_variable v.
                # Check to see if v is some component of split records.
                # If so, generate code.
                #
                fun assign_to_split_record (v, e)
                    = 
                    case (find_split_record_arg v)
                      
                        THE inits
                            =>
                            fold_backward gen e inits
                            where
                                fun gen (SPLIT_RECORD_ITEM
                                         { record, kind, len, offset, 
                                          path, num_vars, consts, ... }, e)
                                    =
                                    {   e =   init_record_item (record, kind, offset, v, path, e);
                                        n =   *num_vars - 1;

                                        num_vars := n;

                                        if (n == 0)
                                             create_record (record, kind, len, consts, e);
                                        else
                                             e;
                                        fi;
                                    };
                            end;

                        NULL => e;
                    esac;

                # -----------------------------------------------------------------
                #  Rebuild
                # 
                #  This function rewrites the nextcode expression and insert spill/reload
                #  code.
                # 
                #  This phase takes O (N) time and O (N) space
                # -----------------------------------------------------------------

                fun rebuild e
                    = 
                    {   fun rewrite_statement (vs, es, f)
                            =
                            {   es =   map rebuild es;

                                my (vs, g)
                                   =
                                   put_reloads vs;

                                g (f(vs, es));
                            };

                        fun rewrite (vs, w, e, f)
                            =
                            {   e =   rebuild e;
                                e =   put_spill (w, e);
                                e =   assign_to_split_record (w, e);

                                my  (vs, g)
                                    =
                                    put_reloads vs;

                                g (f(vs, w, e));
                            };

                        fun rewrite'(vs, wl, e, f)
                            =
                            {   e =   rebuild e;
                                e =   fold_forward put_spill e wl;
                                e =   fold_forward assign_to_split_record e wl;

                                my  (vs, g)
                                    =
                                    put_reloads vs;

                                g (f (vs, wl, e));
                            };

                        fun rewrite_rec (vl, w, e, f)
                            =
                            {   e =   rebuild e;
                                e =   put_spill (w, e);
                                e =   assign_to_split_record (w, e);

                                if (record_is_split w)
                                     e;
                                else f (put_path_reloads vl, w, e);fi;
                            };

                        # Wrappers -- make the match compiler shut up 

                        fun s1 f (v ! vs, es) => f (v, vs, es);
                            s1 _ _ => error "Spill: s1";
                        end;

                        fun e1 f ([v], w, e) => f (v, w, e);
                            e1 _ _ => error "Spill: e1";
                        end;

                        fun s'1 f (vs, [e]) => f (vs, e);
                            s'1 _ _ => error "Spill: s'1";
                        end;

                        fun s'2 f (vs, [x, y]) => f (vs, x, y);
                            s'2 _ _ => error "Spill: s'2";
                        end;


                        # Rewrite the expression
                        #
                        e = case e

                                ncf::TAIL_CALL { fn, args }
                                    => 
                                    rewrite_statement
                                      ( fn ! args,
                                        [],
                                        s1  (\\ (fn, args, _) =  ncf::TAIL_CALL { fn, args })
                                      );

                                ncf::JUMPTABLE { i, xvar, nexts }
                                    => 
                                    rewrite_statement( [i],  nexts,  s1 (\\ (i, _, nexts) = ncf::JUMPTABLE { i, xvar, nexts }));

                                ncf::GET_FIELD_I { i, record, to_temp, type, next }
                                    =>  
                                    rewrite( [record],
                                             to_temp,
                                             next,
                                             e1  (\\ (record, to_temp, next) =  ncf::GET_FIELD_I { i, record, to_temp, type, next })
                                           );

                                ncf::GET_ADDRESS_OF_FIELD_I { i, record, to_temp, next }
                                    =>    
                                    rewrite( [record],
                                             to_temp,
                                             next,
                                             e1  (\\ (record, to_temp, next) = ncf::GET_ADDRESS_OF_FIELD_I { i, record, to_temp,      next }));

                                ncf::DEFINE_RECORD { kind, fields, to_temp, next }
                                    =>     
                                    rewrite_rec (fields, to_temp, next, \\ (fields, to_temp, next) = ncf::DEFINE_RECORD { kind, fields, to_temp, next });

                                ncf::STORE_TO_RAM { op, args, next }
                                    => 
                                    rewrite_statement (args, [e], s'1 (\\ (args, next) = ncf::STORE_TO_RAM { op, args, next }));

                                ncf::FETCH_FROM_RAM { op, args, to_temp, type, next } =>  rewrite (args, to_temp, next,   \\ (args, to_temp, next) = ncf::FETCH_FROM_RAM { op, args, to_temp, type, next });
                                ncf::ARITH           { op, args, to_temp, type, next } =>  rewrite (args, to_temp, next,   \\ (args, to_temp, next) = ncf::ARITH           { op, args, to_temp, type, next });
                                ncf::PURE           { op, args, to_temp, type, next } =>  rewrite (args, to_temp, next,   \\ (args, to_temp, next) = ncf::PURE           { op, args, to_temp, type, next });

                                ncf::RAW_C_CALL { kind, cfun_name, cfun_type, args, to_ttemps, next }
                                    =>  
                                    rewrite'
                                      (
                                        args,
                                        map #1 to_ttemps,
                                        next,
                                        \\ (args, wl, next)
                                            =
                                            ncf::RAW_C_CALL
                                              {
                                                kind, cfun_name, cfun_type, args,
                                                to_ttemps => paired_lists::map
                                                             (\\ (w, (_, t)) = (w, t))
                                                             (wl, to_ttemps),
                                                next
                                              }
                                      );

                                ncf::IF_THEN_ELSE { op, args, xvar, then_next, else_next }
                                    => 
                                    rewrite_statement
                                      ( args,
                                        [then_next, else_next],
                                        s'2  (\\ (args, then_next, else_next) =  ncf::IF_THEN_ELSE { op, args, xvar, then_next, else_next })
                                      );

                                ncf::DEFINE_FUNS _
                                    =>
                                    error "ncf::DEFINE_FUNS in Spill::rebuild";
                            esac;

                        e;
                    };                  # Rebuild 

                # Insert spill/reload code:
                #
                body = rebuild body;
                body = fold_backward put_spill body args;                       # Spill code for arguments.
                body = create_spill_record (num_spills, body);          # Insert spill record creation code:

                if *debug_nextcode_spill_info
                    pr("Nextcode Spill: linearScan done " + i2s num_spills + " spilled\n");
                fi;

                nextcode_fun
                    =
                   (callers_info, f, args, arg_types, body);

                dump("after", nextcode_fun);

                nextcode_fun;
            };                          # fun linear_scan 

        # -------------------------------------------------------------------------
        # spillOne
        # ========
        #
        # This is the driver to process only one nextcode function.
        #
        # This routine takes a total of O (N log N) time and O (N) space
        #
        # -------------------------------------------------------------------------
        fun spill_one  nextcode_fun
            = 
            {   # Perform spilling.
                #
                fun spill_it type_info  nextcode_fun
                    =
                    {   my  { needs_spilling, bandwidth, ... }
                            =
                            needs_spilling  type_info  nextcode_fun; 

                        if *debug_nextcode_spill_info
                             pr("Nextcode Spill bandwidth=" + i2s bandwidth + "\n");
                        fi;

                        if needs_spilling    linear_scan type_info  nextcode_fun;
                        else                                        nextcode_fun;
                        fi;
                    };

                # If we have unboxed floats then
                # we have to distinguish between
                # fpr and gpr registers.  


                (mark_fp_and_rec  nextcode_fun)                         #  Collect fp type info 
                    ->
                    (fp_table, record_table);

                is_moveable_rec =   iht::contains_key record_table;

                nextcode_fun
                    = 
                    if  mp::unboxed_floats
                        #
                        is_fp =   iht::contains_key fp_table;

                        fun is_gp r
                            =
                            not (is_fp r)            and
                            not (is_moveable_rec r);

                        fp =   TYPE_INFO { is_variable=>is_fp, max_live=>maxfpfree, item_size=>2 };
                        gp =   TYPE_INFO { is_variable=>is_gp, max_live=>maxgpfree, item_size=>1 };

                        nextcode_fun =   spill_it  fp  nextcode_fun;                    # Do fp spills first 
                        nextcode_fun =   spill_it  gp  nextcode_fun;                    # Do gp spills 

                        nextcode_fun;
                    else 
                        fun is_gp r
                            =
                            not (is_moveable_rec r);

                        spill_it
                            (TYPE_INFO { is_variable=>is_gp, max_live=>maxgpfree, item_size=>1 })
                            nextcode_fun;
                    fi;

                nextcode_fun;
            };                          # fun spill_one 


        # Main entry point:
        #
        spill_nextcode_registers
            =
            map  spill_one;
    };                                  # spill_nextcode_registers_g 
end;                                    # stipulate


## Copyright 2002 by Bell Laboratories
## Subsequent changes by Jeff Prothero Copyright (c) 2010-2015,
## released per terms of SMLNJ-COPYRIGHT.


Comments and suggestions to: bugs@mythryl.org

PreviousUpNext