m32r.c (m32r_sched_odd_word_p, [...]): Remove.

* config/m32r/m32r.c (m32r_sched_odd_word_p, m32r_adjust_cost,
	m32r_sched_init, m32r_sched_reorder, m32r_variable_issue): Remove.
	(TARGET_SCHED_ADJUST_COST, TARGET_SCHED_VARIABLE_ISSUE,
	TARGET_SCHED_INIT, TARGET_SCHED_REORDER): Don't define.
	* config/m32r/m32r.md: Rewrite the pipeline description as a DFA.

From-SVN: r83829
This commit is contained in:
Steven Bosscher 2004-06-28 22:39:21 +00:00 committed by Steven Bosscher
parent 497be9785f
commit 5ad6fca5c3
4 changed files with 115 additions and 368 deletions

View file

@ -1,3 +1,11 @@
2004-06-28 Steven Bosscher <stevenb@suse.de>
* config/m32r/m32r.c (m32r_sched_odd_word_p, m32r_adjust_cost,
m32r_sched_init, m32r_sched_reorder, m32r_variable_issue): Remove.
(TARGET_SCHED_ADJUST_COST, TARGET_SCHED_VARIABLE_ISSUE,
TARGET_SCHED_INIT, TARGET_SCHED_REORDER): Don't define.
* config/m32r/m32r.md: Rewrite the pipeline description as a DFA.
2004-06-28 Richard Henderson <rth@redhat.com> 2004-06-28 Richard Henderson <rth@redhat.com>
* tree.def (REALPART_EXPR, IMAGPART_EXPR): Change class to 'r'. * tree.def (REALPART_EXPR, IMAGPART_EXPR): Change class to 'r'.

View file

@ -57,9 +57,6 @@ enum m32r_model m32r_model;
const char * m32r_sdata_string = M32R_SDATA_DEFAULT; const char * m32r_sdata_string = M32R_SDATA_DEFAULT;
enum m32r_sdata m32r_sdata; enum m32r_sdata m32r_sdata;
/* Scheduler support */
static int m32r_sched_odd_word_p;
/* Machine-specific symbol_ref flags. */ /* Machine-specific symbol_ref flags. */
#define SYMBOL_FLAG_MODEL_SHIFT SYMBOL_FLAG_MACH_DEP_SHIFT #define SYMBOL_FLAG_MODEL_SHIFT SYMBOL_FLAG_MACH_DEP_SHIFT
#define SYMBOL_REF_MODEL(X) \ #define SYMBOL_REF_MODEL(X) \
@ -92,11 +89,7 @@ static void m32r_output_function_epilogue (FILE *, HOST_WIDE_INT);
static void m32r_file_start (void); static void m32r_file_start (void);
static int m32r_adjust_cost (rtx, rtx, rtx, int);
static int m32r_adjust_priority (rtx, int); static int m32r_adjust_priority (rtx, int);
static void m32r_sched_init (FILE *, int, int);
static int m32r_sched_reorder (FILE *, int, rtx *, int *, int);
static int m32r_variable_issue (FILE *, int, rtx, int);
static int m32r_issue_rate (void); static int m32r_issue_rate (void);
static void m32r_encode_section_info (tree, rtx, int); static void m32r_encode_section_info (tree, rtx, int);
@ -124,18 +117,12 @@ static bool m32r_rtx_costs (rtx, int, int, int *);
#undef TARGET_ASM_FILE_START #undef TARGET_ASM_FILE_START
#define TARGET_ASM_FILE_START m32r_file_start #define TARGET_ASM_FILE_START m32r_file_start
#undef TARGET_SCHED_ADJUST_COST
#define TARGET_SCHED_ADJUST_COST m32r_adjust_cost
#undef TARGET_SCHED_ADJUST_PRIORITY #undef TARGET_SCHED_ADJUST_PRIORITY
#define TARGET_SCHED_ADJUST_PRIORITY m32r_adjust_priority #define TARGET_SCHED_ADJUST_PRIORITY m32r_adjust_priority
#undef TARGET_SCHED_ISSUE_RATE #undef TARGET_SCHED_ISSUE_RATE
#define TARGET_SCHED_ISSUE_RATE m32r_issue_rate #define TARGET_SCHED_ISSUE_RATE m32r_issue_rate
#undef TARGET_SCHED_VARIABLE_ISSUE #undef TARGET_SCHED_USE_DFA_PIPELINE_INTERFACE
#define TARGET_SCHED_VARIABLE_ISSUE m32r_variable_issue #define TARGET_SCHED_USE_DFA_PIPELINE_INTERFACE hook_int_void_1
#undef TARGET_SCHED_INIT
#define TARGET_SCHED_INIT m32r_sched_init
#undef TARGET_SCHED_REORDER
#define TARGET_SCHED_REORDER m32r_sched_reorder
#undef TARGET_ENCODE_SECTION_INFO #undef TARGET_ENCODE_SECTION_INFO
#define TARGET_ENCODE_SECTION_INFO m32r_encode_section_info #define TARGET_ENCODE_SECTION_INFO m32r_encode_section_info
@ -1463,14 +1450,6 @@ m32r_va_arg (tree valist, tree type)
return addr_rtx; return addr_rtx;
} }
static int
m32r_adjust_cost (rtx insn ATTRIBUTE_UNUSED, rtx link ATTRIBUTE_UNUSED,
rtx dep_insn ATTRIBUTE_UNUSED, int cost)
{
return cost;
}
/* Return true if INSN is real instruction bearing insn. */ /* Return true if INSN is real instruction bearing insn. */
@ -1497,124 +1476,6 @@ m32r_adjust_priority (rtx insn, int priority)
} }
/* Initialize for scheduling a group of instructions. */
static void
m32r_sched_init (FILE * stream ATTRIBUTE_UNUSED,
int verbose ATTRIBUTE_UNUSED,
int max_ready ATTRIBUTE_UNUSED)
{
m32r_sched_odd_word_p = FALSE;
}
/* Reorder the schedulers priority list if needed */
static int
m32r_sched_reorder (FILE * stream, int verbose, rtx * ready,
int *n_readyp, int clock ATTRIBUTE_UNUSED)
{
int n_ready = *n_readyp;
if (TARGET_DEBUG)
return m32r_issue_rate ();
if (verbose <= 7)
stream = (FILE *)0;
if (stream)
fprintf (stream,
";;\t\t::: Looking at %d insn(s) on ready list, boundary is %s word\n",
n_ready,
(m32r_sched_odd_word_p) ? "odd" : "even");
if (n_ready > 1)
{
rtx * long_head = alloca (sizeof (rtx) * n_ready);
rtx * long_tail = long_head;
rtx * short_head = alloca (sizeof (rtx) * n_ready);
rtx * short_tail = short_head;
rtx * new_head = alloca (sizeof (rtx) * n_ready);
rtx * new_tail = new_head + (n_ready - 1);
int i;
/* Loop through the instructions, classifying them as short/long. Try
to keep 2 short together and/or 1 long. Note, the ready list is
actually ordered backwards, so keep it in that manner. */
for (i = n_ready-1; i >= 0; i--)
{
rtx insn = ready[i];
if (! m32r_is_insn (insn))
{
/* Dump all current short/long insns just in case. */
while (long_head != long_tail)
*new_tail-- = *long_head++;
while (short_head != short_tail)
*new_tail-- = *short_head++;
*new_tail-- = insn;
if (stream)
fprintf (stream,
";;\t\t::: Skipping non instruction %d\n",
INSN_UID (insn));
}
else
{
if (get_attr_insn_size (insn) != INSN_SIZE_SHORT)
*long_tail++ = insn;
else
*short_tail++ = insn;
}
}
/* If we are on an odd word, emit a single short instruction if
we can. */
if (m32r_sched_odd_word_p && short_head != short_tail)
*new_tail-- = *short_head++;
/* Now dump out all of the long instructions. */
while (long_head != long_tail)
*new_tail-- = *long_head++;
/* Now dump out all of the short instructions. */
while (short_head != short_tail)
*new_tail-- = *short_head++;
if (new_tail + 1 != new_head)
abort ();
memcpy (ready, new_head, sizeof (rtx) * n_ready);
if (stream)
{
int i;
fprintf (stream, ";;\t\t::: New ready list: ");
for (i = 0; i < n_ready; i++)
{
rtx insn = ready[i];
fprintf (stream, " %d", INSN_UID (ready[i]));
if (! m32r_is_insn (insn))
fputs ("(?)", stream);
else if (get_attr_insn_size (insn) != INSN_SIZE_SHORT)
fputs ("(l)", stream);
else
fputs ("(s)", stream);
}
fprintf (stream, "\n");
}
}
return m32r_issue_rate ();
}
/* Indicate how many instructions can be issued at the same time. /* Indicate how many instructions can be issued at the same time.
This is sort of a lie. The m32r can issue only 1 long insn at This is sort of a lie. The m32r can issue only 1 long insn at
once, but it can issue 2 short insns. The default therefore is once, but it can issue 2 short insns. The default therefore is
@ -1626,45 +1487,6 @@ m32r_issue_rate (void)
{ {
return ((TARGET_LOW_ISSUE_RATE) ? 1 : 2); return ((TARGET_LOW_ISSUE_RATE) ? 1 : 2);
} }
/* If we have a machine that can issue a variable # of instructions
per cycle, indicate how many more instructions can be issued
after the current one. */
static int
m32r_variable_issue (FILE * stream, int verbose, rtx insn, int how_many)
{
int orig_odd_word_p = m32r_sched_odd_word_p;
int short_p = FALSE;
how_many--;
if (how_many > 0 && !TARGET_DEBUG)
{
if (! m32r_is_insn (insn))
how_many++;
else if (get_attr_insn_size (insn) != INSN_SIZE_SHORT)
{
how_many = 0;
m32r_sched_odd_word_p = 0;
}
else
{
m32r_sched_odd_word_p = !m32r_sched_odd_word_p;
short_p = TRUE;
}
}
if (verbose > 7 && stream)
fprintf (stream,
";;\t\t::: %s insn %d starts on an %s word, can emit %d more instruction(s)\n",
short_p ? "short" : "long",
INSN_UID (insn),
orig_odd_word_p ? "odd" : "even",
how_many);
return how_many;
}
/* Cost functions. */ /* Cost functions. */

View file

@ -60,225 +60,142 @@
[(set_attr "length" "4") [(set_attr "length" "4")
(set_attr "type" "multi")]) (set_attr "type" "multi")])
;; Whether an instruction is short (16-bit) or long (32-bit).
;; Whether an instruction is 16-bit or 32-bit
(define_attr "insn_size" "short,long" (define_attr "insn_size" "short,long"
(if_then_else (eq_attr "type" "int2,load2,store2,shift2,mul2") (if_then_else (eq_attr "type" "int2,load2,store2,shift2,mul2")
(const_string "short") (const_string "short")
(const_string "long"))) (const_string "long")))
(define_attr "debug" "no,yes" ;; The target CPU we're compiling for.
(const (symbol_ref "(TARGET_DEBUG != 0)"))) (define_attr "cpu" "m32r,m32r2,m32rx"
(cond [(ne (symbol_ref "TARGET_M32RX") (const_int 0))
(define_attr "opt_size" "no,yes" (const_string "m32rx")
(const (symbol_ref "(optimize_size != 0)"))) (ne (symbol_ref "TARGET_M32R2") (const_int 0))
(const_string "m32r2")]
(define_attr "m32r" "no,yes" (const_string "m32r")))
(const (symbol_ref "(TARGET_M32R != 0)")))
(define_attr "m32rx" "no,yes"
(const (symbol_ref "(TARGET_M32RX != 0)")))
(define_attr "m32r2" "no,yes"
(const (symbol_ref "(TARGET_M32R2 != 0)")))
(define_attr "m32rx_pipeline" "either,s,o,long,m32r"
(cond [(and (eq_attr "m32rx" "no")
(eq_attr "m32r2" "no"))
(const_string "m32r")
;; Defines the pipeline where an instruction can be executed on.
;; For the M32R, a short instruction can execute one of the two pipes.
;; For the M32Rx, the restrictions are modelled in the second
;; condition of this attribute definition.
(define_attr "m32r_pipeline" "either,s,o,long,m32r"
(cond [(and (eq_attr "cpu" "m32r")
(eq_attr "insn_size" "short"))
(const_string "either")
(eq_attr "insn_size" "!short") (eq_attr "insn_size" "!short")
(const_string "long")] (const_string "long")]
(cond [(eq_attr "type" "int2")
(cond [(eq_attr "type" "int2") (const_string "either")
(const_string "either") (eq_attr "type" "load2,store2,shift2,uncond_branch,branch,call")
(const_string "o")
(eq_attr "type" "load2,store2,shift2,uncond_branch,branch,call") (eq_attr "type" "mul2")
(const_string "o") (const_string "s")]
(const_string "long"))))
(eq_attr "type" "mul2")
(const_string "s")]
(const_string "long"))))
;; :::::::::::::::::::: ;; ::::::::::::::::::::
;; :: ;; ::
;; :: Function Units ;; :: Pipeline description
;; :: ;; ::
;; :::::::::::::::::::: ;; ::::::::::::::::::::
;; On most RISC machines, there are instructions whose results are not ;; This model is based on Chapter 2, Appendix 3 and Appendix 4 of the
;; available for a specific number of cycles. Common cases are instructions ;; "M32R-FPU Software Manual", Revision 1.01, plus additional information
;; that load data from memory. On many machines, a pipeline stall will result ;; obtained by our best friend and mine, Google.
;; if the data is referenced too soon after the load instruction.
;; In addition, many newer microprocessors have multiple function units,
;; usually one for integer and one for floating point, and often will incur
;; pipeline stalls when a result that is needed is not yet ready.
;; The descriptions in this section allow the specification of how much time
;; must elapse between the execution of an instruction and the time when its
;; result is used. It also allows specification of when the execution of an
;; instruction will delay execution of similar instructions due to function
;; unit conflicts.
;; For the purposes of the specifications in this section, a machine is divided
;; into "function units", each of which execute a specific class of
;; instructions in first-in-first-out order. Function units that accept one
;; instruction each cycle and allow a result to be used in the succeeding
;; instruction (usually via forwarding) need not be specified. Classic RISC
;; microprocessors will normally have a single function unit, which we can call
;; `memory'. The newer "superscalar" processors will often have function units
;; for floating point operations, usually at least a floating point adder and
;; multiplier.
;; Each usage of a function units by a class of insns is specified with a
;; `define_function_unit' expression, which looks like this:
;; (define_function_unit NAME MULTIPLICITY SIMULTANEITY TEST READY-DELAY
;; ISSUE-DELAY [CONFLICT-LIST])
;; NAME is a string giving the name of the function unit.
;; MULTIPLICITY is an integer specifying the number of identical units in the
;; processor. If more than one unit is specified, they will be scheduled
;; independently. Only truly independent units should be counted; a pipelined
;; unit should be specified as a single unit. (The only common example of a
;; machine that has multiple function units for a single instruction class that
;; are truly independent and not pipelined are the two multiply and two
;; increment units of the CDC 6600.)
;; SIMULTANEITY specifies the maximum number of insns that can be executing in
;; each instance of the function unit simultaneously or zero if the unit is
;; pipelined and has no limit.
;; All `define_function_unit' definitions referring to function unit NAME must
;; have the same name and values for MULTIPLICITY and SIMULTANEITY.
;; TEST is an attribute test that selects the insns we are describing in this
;; definition. Note that an insn may use more than one function unit and a
;; function unit may be specified in more than one `define_function_unit'.
;; READY-DELAY is an integer that specifies the number of cycles after which
;; the result of the instruction can be used without introducing any stalls.
;; ISSUE-DELAY is an integer that specifies the number of cycles after the
;; instruction matching the TEST expression begins using this unit until a
;; subsequent instruction can begin. A cost of N indicates an N-1 cycle delay.
;; A subsequent instruction may also be delayed if an earlier instruction has a
;; longer READY-DELAY value. This blocking effect is computed using the
;; SIMULTANEITY, READY-DELAY, ISSUE-DELAY, and CONFLICT-LIST terms. For a
;; normal non-pipelined function unit, SIMULTANEITY is one, the unit is taken
;; to block for the READY-DELAY cycles of the executing insn, and smaller
;; values of ISSUE-DELAY are ignored.
;; CONFLICT-LIST is an optional list giving detailed conflict costs for this
;; unit. If specified, it is a list of condition test expressions to be
;; applied to insns chosen to execute in NAME following the particular insn
;; matching TEST that is already executing in NAME. For each insn in the list,
;; ISSUE-DELAY specifies the conflict cost; for insns not in the list, the cost
;; is zero. If not specified, CONFLICT-LIST defaults to all instructions that
;; use the function unit.
;; Typical uses of this vector are where a floating point function unit can
;; pipeline either single- or double-precision operations, but not both, or
;; where a memory unit can pipeline loads, but not stores, etc.
;; As an example, consider a classic RISC machine where the result of a load
;; instruction is not available for two cycles (a single "delay" instruction is
;; required) and where only one load instruction can be executed
;; simultaneously. This would be specified as:
;; (define_function_unit "memory" 1 1 (eq_attr "type" "load") 2 0)
;; For the case of a floating point function unit that can pipeline
;; either single or double precision, but not both, the following could be
;; specified:
;; ;;
;; (define_function_unit "fp" 1 0 ;; The pipeline is modelled as a fetch unit, and a core with a memory unit,
;; (eq_attr "type" "sp_fp") 4 4 ;; two execution units, where "fetch" models IF and D, "memory" for MEM1
;; [(eq_attr "type" "dp_fp")]) ;; and MEM2, and "EXEC" for E, E1, E2, EM, and EA. Writeback and
;; bypasses are not modelled.
(define_automaton "m32r")
;; We pretend there are two short (16 bits) instruction fetchers. The
;; "s" short fetcher cannot be reserved until the "o" short fetcher is
;; reserved. Some instructions reserve both the left and right fetchers.
;; These fetch units are a hack to get GCC to better pack the instructions
;; for the M32Rx processor, which has two execution pipes.
;; ;;
;; (define_function_unit "fp" 1 0 ;; In reality there is only one decoder, which can decode either two 16 bits
;; (eq_attr "type" "dp_fp") 4 4 ;; instructions, or a single 32 bits instruction.
;; [(eq_attr "type" "sp_fp")]) ;;
;; Note, "fetch" models both the IF and the D pipeline stages.
;;
;; The m32rx core has two execution pipes. We name them o_E and s_E.
;; In addition, there's a memory unit.
;; Note: The scheduler attempts to avoid function unit conflicts and uses all (define_cpu_unit "o_IF,s_IF,o_E,s_E,memory" "m32r")
;; the specifications in the `define_function_unit' expression. It has
;; recently come to our attention that these specifications may not allow
;; modeling of some of the newer "superscalar" processors that have insns using
;; multiple pipelined units. These insns will cause a potential conflict for
;; the second unit used during their execution and there is no way of
;; representing that conflict. We welcome any examples of how function unit
;; conflicts work in such processors and suggestions for their representation.
;; Function units of the M32R ;; Prevent the s pipe from being reserved before the o pipe.
;; Units that take one cycle do not need to be specified. (final_presence_set "s_IF" "o_IF")
(final_presence_set "s_E" "o_E")
;; (define_function_unit {name} {multiplicity} {simultaneity} {test} ;; On the M32Rx, long instructions execute on both pipes, so reserve
;; {ready-delay} {issue-delay} [{conflict-list}]) ;; both fetch slots and both pipes.
(define_reservation "long_IF" "o_IF+s_IF")
(define_reservation "long_E" "o_E+s_E")
;; Hack to get GCC to better pack the instructions. ;; ::::::::::::::::::::
;; We pretend there is a separate long function unit that conflicts with
;; both the left and right 16 bit insn slots.
(define_function_unit "short" 2 2 ;; Simple instructions do 4 stages: IF D E WB. WB is not modelled.
(and (eq_attr "m32r" "yes") ;; Hence, ready latency is 1.
(define_insn_reservation "short_left" 1
(and (eq_attr "m32r_pipeline" "o")
(and (eq_attr "insn_size" "short") (and (eq_attr "insn_size" "short")
(eq_attr "type" "!load2"))) (eq_attr "type" "!load2")))
1 0 "o_IF,o_E")
[(eq_attr "insn_size" "long")])
(define_function_unit "short" 2 2 ;; load delay of 1 clock for mem execution + 1 clock for WB (define_insn_reservation "short_right" 1
(and (eq_attr "m32r" "yes") (and (eq_attr "m32r_pipeline" "s")
(eq_attr "type" "load2")) (and (eq_attr "insn_size" "short")
3 0 (eq_attr "type" "!load2")))
[(eq_attr "insn_size" "long")]) "s_IF,s_E")
(define_function_unit "long" 1 1 (define_insn_reservation "short_either" 1
(and (eq_attr "m32r" "yes") (and (eq_attr "m32r_pipeline" "either")
(and (eq_attr "insn_size" "short")
(eq_attr "type" "!load2")))
"o_IF|s_IF,o_E|s_E")
(define_insn_reservation "long_m32r" 1
(and (eq_attr "cpu" "m32r")
(and (eq_attr "insn_size" "long") (and (eq_attr "insn_size" "long")
(eq_attr "type" "!load4,load8"))) (eq_attr "type" "!load4,load8")))
1 0 "long_IF,long_E")
[(eq_attr "insn_size" "short")])
(define_function_unit "long" 1 1 ;; load delay of 1 clock for mem execution + 1 clock for WB (define_insn_reservation "long_m32rx" 2
(and (eq_attr "m32r" "yes") (and (eq_attr "m32r_pipeline" "long")
(and (eq_attr "insn_size" "long")
(eq_attr "type" "load4,load8")))
3 0
[(eq_attr "insn_size" "short")])
(define_function_unit "left" 1 1
(and (eq_attr "m32rx_pipeline" "o,either")
(eq_attr "type" "!load2"))
1 0
[(eq_attr "insn_size" "long")])
(define_function_unit "left" 1 1 ;; load delay of 1 clock for mem execution + 1 clock for WB
(and (eq_attr "m32rx_pipeline" "o,either")
(eq_attr "type" "load2"))
3 0
[(eq_attr "insn_size" "long")])
(define_function_unit "right" 1 1
(eq_attr "m32rx_pipeline" "s,either")
1 0
[(eq_attr "insn_size" "long")])
(define_function_unit "long" 1 1
(and (eq_attr "m32rx" "yes")
(and (eq_attr "insn_size" "long") (and (eq_attr "insn_size" "long")
(eq_attr "type" "!load4,load8"))) (eq_attr "type" "!load4,load8")))
2 0 "long_IF,long_E")
[(eq_attr "insn_size" "short")])
(define_function_unit "long" 1 1 ;; load delay of 1 clock for mem execution + 1 clock for WB ;; Load/store instructions do 6 stages: IF D E MEM1 MEM2 WB.
(and (eq_attr "m32rx" "yes") ;; MEM1 may require more than one cycle depending on locality. We
;; optimistically assume all memory is nearby, ie. MEM1 takes only
;; one cycle. Hence, ready latency is 3.
;; The M32Rx can do short load/store only on the left pipe.
(define_insn_reservation "short_load_left" 3
(and (eq_attr "m32r_pipeline" "o")
(and (eq_attr "insn_size" "short")
(eq_attr "type" "load2")))
"o_IF,o_E,memory*2")
(define_insn_reservation "short_load" 3
(and (eq_attr "m32r_pipeline" "either")
(and (eq_attr "insn_size" "short")
(eq_attr "type" "load2")))
"s_IF|o_IF,s_E|o_E,memory*2")
(define_insn_reservation "long_load" 3
(and (eq_attr "cpu" "m32r")
(and (eq_attr "insn_size" "long") (and (eq_attr "insn_size" "long")
(eq_attr "type" "load4,load8"))) (eq_attr "type" "load4,load8")))
3 0 "long_IF,long_E,memory*2")
[(eq_attr "insn_size" "short")])
(define_insn_reservation "long_load_m32rx" 3
(and (eq_attr "m32r_pipeline" "long")
(eq_attr "type" "load4,load8"))
"long_IF,long_E,memory*2")
;; Expand prologue as RTL ;; Expand prologue as RTL
(define_expand "prologue" (define_expand "prologue"

View file

@ -233,7 +233,7 @@ struct gcc_target
rtx (* dfa_post_cycle_insn) (void); rtx (* dfa_post_cycle_insn) (void);
/* The following member value is a pointer to a function returning value /* The following member value is a pointer to a function returning value
which defines how many insns in queue `ready' will we try for which defines how many insns in queue `ready' will we try for
multi-pass scheduling. if the member value is nonzero and the multi-pass scheduling. If the member value is nonzero and the
function returns positive value, the DFA based scheduler will make function returns positive value, the DFA based scheduler will make
multi-pass scheduling for the first cycle. In other words, we will multi-pass scheduling for the first cycle. In other words, we will
try to choose ready insn which permits to start maximum number of try to choose ready insn which permits to start maximum number of