X-Git-Url: http://git.hcoop.net/bpt/emacs.git/blobdiff_plain/d06714cb449d3eb3f58f4ed79053ca173db286fa..3438fe218c77633ee2c5f106e3a335f906347247:/src/alloc.c diff --git a/src/alloc.c b/src/alloc.c index 29aabdd461..27426cdff6 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -161,6 +161,10 @@ static pthread_mutex_t alloc_mutex; #define GC_STRING_BYTES(S) (STRING_BYTES (S)) +/* Default value of gc_cons_threshold (see below). */ + +#define GC_DEFAULT_THRESHOLD (100000 * sizeof (Lisp_Object)) + /* Global variables. */ struct emacs_globals globals; @@ -258,6 +262,7 @@ static char *stack_copy; static ptrdiff_t stack_copy_size; #endif +static Lisp_Object Qstring_bytes, Qvector_slots, Qheap; static Lisp_Object Qgc_cons_threshold; Lisp_Object Qchar_table_extra_slots; @@ -2389,7 +2394,7 @@ LENGTH must be a number. INIT matters only in whether it is t or nil. */) /* Clear any extraneous bits in the last byte. */ p->data[length_in_chars - 1] - &= (1 << (XINT (length) % BOOL_VECTOR_BITS_PER_CHAR)) - 1; + &= (1 << ((XFASTINT (length) - 1) % BOOL_VECTOR_BITS_PER_CHAR + 1)) - 1; } return val; @@ -2710,6 +2715,7 @@ free_cons (struct Lisp_Cons *ptr) ptr->car = Vdead; #endif cons_free_list = ptr; + consing_since_gc -= sizeof *ptr; total_free_conses++; } @@ -2805,6 +2811,38 @@ list5 (Lisp_Object arg1, Lisp_Object arg2, Lisp_Object arg3, Lisp_Object arg4, L Fcons (arg5, Qnil))))); } +/* Make a list of COUNT Lisp_Objects, where ARG is the + first one. Allocate conses from pure space if TYPE + is CONSTYPE_PURE, or allocate as usual if type is CONSTYPE_HEAP. */ + +Lisp_Object +listn (enum constype type, ptrdiff_t count, Lisp_Object arg, ...) +{ + va_list ap; + ptrdiff_t i; + Lisp_Object val, *objp; + + /* Change to SAFE_ALLOCA if you hit this eassert. */ + eassert (count <= MAX_ALLOCA / sizeof (Lisp_Object)); + + objp = alloca (count * sizeof (Lisp_Object)); + objp[0] = arg; + va_start (ap, arg); + for (i = 1; i < count; i++) + objp[i] = va_arg (ap, Lisp_Object); + va_end (ap); + + for (i = 0, val = Qnil; i < count; i++) + { + if (type == CONSTYPE_PURE) + val = pure_cons (objp[i], val); + else if (type == CONSTYPE_HEAP) + val = Fcons (objp[i], val); + else + abort (); + } + return val; +} DEFUN ("list", Flist, Slist, 0, MANY, 0, doc: /* Return a newly created list with specified arguments as elements. @@ -2937,7 +2975,7 @@ verify (VECTOR_BLOCK_SIZE <= (1 << PSEUDOVECTOR_SIZE_BITS)); eassert ((index) < VECTOR_MAX_FREE_LIST_INDEX); \ (v)->header.next.vector = vector_free_lists[index]; \ vector_free_lists[index] = (v); \ - total_free_vector_bytes += (nbytes); \ + total_free_vector_slots += (nbytes) / word_size; \ } while (0) struct vector_block @@ -2967,9 +3005,9 @@ Lisp_Object zero_vector; static EMACS_INT total_vectors; -/* Number of bytes used by live and free vectors. */ +/* Total size of live and free vectors, in Lisp_Object units. */ -static EMACS_INT total_vector_bytes, total_free_vector_bytes; +static EMACS_INT total_vector_slots, total_free_vector_slots; /* Get a new vector block. */ @@ -3016,7 +3054,7 @@ allocate_vector_from_block (size_t nbytes) vector = vector_free_lists[index]; vector_free_lists[index] = vector->header.next.vector; vector->header.next.nbytes = nbytes; - total_free_vector_bytes -= nbytes; + total_free_vector_slots -= nbytes / word_size; return vector; } @@ -3031,7 +3069,7 @@ allocate_vector_from_block (size_t nbytes) vector = vector_free_lists[index]; vector_free_lists[index] = vector->header.next.vector; vector->header.next.nbytes = nbytes; - total_free_vector_bytes -= nbytes; + total_free_vector_slots -= nbytes / word_size; /* Excess bytes are used for the smaller vector, which should be set on an appropriate free list. */ @@ -3085,7 +3123,7 @@ sweep_vectors (void) struct vector_block *block = vector_blocks, **bprev = &vector_blocks; struct Lisp_Vector *vector, *next, **vprev = &large_vectors; - total_vectors = total_vector_bytes = total_free_vector_bytes = 0; + total_vectors = total_vector_slots = total_free_vector_slots = 0; memset (vector_free_lists, 0, sizeof (vector_free_lists)); /* Looking through vector blocks. */ @@ -3101,7 +3139,7 @@ sweep_vectors (void) { VECTOR_UNMARK (vector); total_vectors++; - total_vector_bytes += vector->header.next.nbytes; + total_vector_slots += vector->header.next.nbytes / word_size; next = ADVANCE (vector, vector->header.next.nbytes); } else @@ -3167,14 +3205,14 @@ sweep_vectors (void) pseudovector type grows beyond VBLOCK_BYTES_MAX. */ eassert (PSEUDOVECTOR_TYPEP (&vector->header, PVEC_BOOL_VECTOR)); - total_vector_bytes + total_vector_slots += (bool_header_size + ((b->size + BOOL_VECTOR_BITS_PER_CHAR - 1) - / BOOL_VECTOR_BITS_PER_CHAR)); + / BOOL_VECTOR_BITS_PER_CHAR)) / word_size; } else - total_vector_bytes += (header_size - + vector->header.size * word_size); + total_vector_slots + += header_size / word_size + vector->header.size; vprev = &vector->header.next.vector; } else @@ -3558,10 +3596,10 @@ static int marker_block_index = MARKER_BLOCK_SIZE; static union Lisp_Misc *marker_free_list; -/* Return a newly allocated Lisp_Misc object, with no substructure. */ +/* Return a newly allocated Lisp_Misc object of specified TYPE. */ -Lisp_Object -allocate_misc (void) +static Lisp_Object +allocate_misc (enum Lisp_Misc_Type type) { Lisp_Object val; @@ -3593,6 +3631,7 @@ allocate_misc (void) --total_free_markers; consing_since_gc += sizeof (union Lisp_Misc); misc_objects_consed++; + XMISCTYPE (val) = type; XMISCANY (val)->gcmarkbit = 0; return val; } @@ -3605,7 +3644,7 @@ free_misc (Lisp_Object misc) XMISCTYPE (misc) = Lisp_Misc_Free; XMISC (misc)->u_free.chain = marker_free_list; marker_free_list = XMISC (misc); - + consing_since_gc -= sizeof (union Lisp_Misc); total_free_markers++; } @@ -3619,8 +3658,7 @@ make_save_value (void *pointer, ptrdiff_t integer) register Lisp_Object val; register struct Lisp_Save_Value *p; - val = allocate_misc (); - XMISCTYPE (val) = Lisp_Misc_Save_Value; + val = allocate_misc (Lisp_Misc_Save_Value); p = XSAVE_VALUE (val); p->pointer = pointer; p->integer = integer; @@ -3628,6 +3666,21 @@ make_save_value (void *pointer, ptrdiff_t integer) return val; } +/* Return a Lisp_Misc_Overlay object with specified START, END and PLIST. */ + +Lisp_Object +build_overlay (Lisp_Object start, Lisp_Object end, Lisp_Object plist) +{ + register Lisp_Object overlay; + + overlay = allocate_misc (Lisp_Misc_Overlay); + OVERLAY_START (overlay) = start; + OVERLAY_END (overlay) = end; + OVERLAY_PLIST (overlay) = plist; + XOVERLAY (overlay)->next = NULL; + return overlay; +} + DEFUN ("make-marker", Fmake_marker, Smake_marker, 0, 0, 0, doc: /* Return a newly allocated marker which does not point at any place. */) (void) @@ -3635,8 +3688,7 @@ DEFUN ("make-marker", Fmake_marker, Smake_marker, 0, 0, 0, register Lisp_Object val; register struct Lisp_Marker *p; - val = allocate_misc (); - XMISCTYPE (val) = Lisp_Misc_Marker; + val = allocate_misc (Lisp_Misc_Marker); p = XMARKER (val); p->buffer = 0; p->bytepos = 0; @@ -3661,8 +3713,7 @@ build_marker (struct buffer *buf, ptrdiff_t charpos, ptrdiff_t bytepos) /* Every character is at least one byte. */ eassert (charpos <= bytepos); - obj = allocate_misc (); - XMISCTYPE (obj) = Lisp_Misc_Marker; + obj = allocate_misc (Lisp_Misc_Marker); m = XMARKER (obj); m->buffer = buf; m->charpos = charpos; @@ -5377,25 +5428,25 @@ DEFUN ("garbage-collect", Fgarbage_collect, Sgarbage_collect, 0, 0, "", doc: /* Reclaim storage for Lisp objects no longer needed. Garbage collection happens automatically if you cons more than `gc-cons-threshold' bytes of Lisp data since previous garbage collection. -`garbage-collect' normally returns a list with info on amount of space in use: - ((CONS INTERNAL-SIZE USED-CONSES FREE-CONSES) - (SYMBOL INTERNAL-SIZE USED-SYMBOLS FREE-SYMBOLS) - (MISC INTERNAL-SIZE USED-MISCS FREE-MISCS) - (STRING INTERNAL-SIZE USED-STRINGS USED-STRING-BYTES FREE-STRING) - (VECTOR INTERNAL-SIZE USED-VECTORS USED-VECTOR-BYTES FREE-VECTOR-BYTES) - (FLOAT INTERNAL-SIZE USED-FLOATS FREE-FLOATS) - (INTERVAL INTERNAL-SIZE USED-INTERVALS FREE-INTERVALS) - (BUFFER INTERNAL-SIZE USED-BUFFERS)) +`garbage-collect' normally returns a list with info on amount of space in use, +where each entry has the form (NAME SIZE USED FREE), where: +- NAME is a symbol describing the kind of objects this entry represents, +- SIZE is the number of bytes used by each one, +- USED is the number of those objects that were found live in the heap, +- FREE is the number of those objects that are not live but that Emacs + keeps around for future allocations (maybe because it does not know how + to return them to the OS). However, if there was overflow in pure space, `garbage-collect' returns nil, because real GC can't be done. See Info node `(elisp)Garbage Collection'. */) (void) { register struct specbinding *bind; + register struct buffer *nextb; char stack_top_variable; ptrdiff_t i; int message_p; - Lisp_Object total[8]; + Lisp_Object total[11]; ptrdiff_t count = SPECPDL_INDEX (); EMACS_TIME t1; @@ -5411,40 +5462,8 @@ See Info node `(elisp)Garbage Collection'. */) /* Don't keep undo information around forever. Do this early on, so it is no problem if the user quits. */ - { - register struct buffer *nextb = all_buffers; - - while (nextb) - { - /* If a buffer's undo list is Qt, that means that undo is - turned off in that buffer. Calling truncate_undo_list on - Qt tends to return NULL, which effectively turns undo back on. - So don't call truncate_undo_list if undo_list is Qt. */ - if (! NILP (nextb->BUFFER_INTERNAL_FIELD (name)) - && ! EQ (nextb->BUFFER_INTERNAL_FIELD (undo_list), Qt)) - truncate_undo_list (nextb); - - /* Shrink buffer gaps, but skip indirect and dead buffers. */ - if (nextb->base_buffer == 0 && !NILP (nextb->BUFFER_INTERNAL_FIELD (name)) - && ! nextb->text->inhibit_shrinking) - { - /* If a buffer's gap size is more than 10% of the buffer - size, or larger than 2000 bytes, then shrink it - accordingly. Keep a minimum size of 20 bytes. */ - int size = min (2000, max (20, (nextb->text->z_byte / 10))); - - if (nextb->text->gap_size > size) - { - struct buffer *save_current = current_buffer; - current_buffer = nextb; - make_gap (-(nextb->text->gap_size - size)); - current_buffer = save_current; - } - } - - nextb = nextb->header.next.buffer; - } - } + FOR_EACH_BUFFER (nextb) + compact_buffer (nextb); t1 = current_emacs_time (); @@ -5493,8 +5512,6 @@ See Info node `(elisp)Garbage Collection'. */) gc_in_progress = 1; - /* clear_marks (); */ - /* Mark all the special slots that serve as the roots of accessibility. */ for (i = 0; i < staticidx; i++) @@ -5558,48 +5575,42 @@ See Info node `(elisp)Garbage Collection'. */) Look thru every buffer's undo list for elements that update markers that were not marked, and delete them. */ - { - register struct buffer *nextb = all_buffers; - - while (nextb) - { - /* If a buffer's undo list is Qt, that means that undo is - turned off in that buffer. Calling truncate_undo_list on - Qt tends to return NULL, which effectively turns undo back on. - So don't call truncate_undo_list if undo_list is Qt. */ - if (! EQ (nextb->BUFFER_INTERNAL_FIELD (undo_list), Qt)) - { - Lisp_Object tail, prev; - tail = nextb->BUFFER_INTERNAL_FIELD (undo_list); - prev = Qnil; - while (CONSP (tail)) - { - if (CONSP (XCAR (tail)) - && MARKERP (XCAR (XCAR (tail))) - && !XMARKER (XCAR (XCAR (tail)))->gcmarkbit) - { - if (NILP (prev)) - nextb->BUFFER_INTERNAL_FIELD (undo_list) = tail = XCDR (tail); - else - { - tail = XCDR (tail); - XSETCDR (prev, tail); - } - } - else - { - prev = tail; - tail = XCDR (tail); - } - } - } - /* Now that we have stripped the elements that need not be in the - undo_list any more, we can finally mark the list. */ - mark_object (nextb->BUFFER_INTERNAL_FIELD (undo_list)); - - nextb = nextb->header.next.buffer; - } - } + FOR_EACH_BUFFER (nextb) + { + /* If a buffer's undo list is Qt, that means that undo is + turned off in that buffer. Calling truncate_undo_list on + Qt tends to return NULL, which effectively turns undo back on. + So don't call truncate_undo_list if undo_list is Qt. */ + if (! EQ (nextb->BUFFER_INTERNAL_FIELD (undo_list), Qt)) + { + Lisp_Object tail, prev; + tail = nextb->BUFFER_INTERNAL_FIELD (undo_list); + prev = Qnil; + while (CONSP (tail)) + { + if (CONSP (XCAR (tail)) + && MARKERP (XCAR (XCAR (tail))) + && !XMARKER (XCAR (XCAR (tail)))->gcmarkbit) + { + if (NILP (prev)) + nextb->BUFFER_INTERNAL_FIELD (undo_list) = tail = XCDR (tail); + else + { + tail = XCDR (tail); + XSETCDR (prev, tail); + } + } + else + { + prev = tail; + tail = XCDR (tail); + } + } + } + /* Now that we have stripped the elements that need not be in the + undo_list any more, we can finally mark the list. */ + mark_object (nextb->BUFFER_INTERNAL_FIELD (undo_list)); + } gc_sweep (); @@ -5617,12 +5628,11 @@ See Info node `(elisp)Garbage Collection'. */) CHECK_CONS_LIST (); - /* clear_marks (); */ gc_in_progress = 0; consing_since_gc = 0; - if (gc_cons_threshold < 10000) - gc_cons_threshold = 10000; + if (gc_cons_threshold < GC_DEFAULT_THRESHOLD / 10) + gc_cons_threshold = GC_DEFAULT_THRESHOLD / 10; gc_relative_threshold = 0; if (FLOATP (Vgc_cons_percentage)) @@ -5633,7 +5643,7 @@ See Info node `(elisp)Garbage Collection'. */) tot += total_symbols * sizeof (struct Lisp_Symbol); tot += total_markers * sizeof (union Lisp_Misc); tot += total_string_bytes; - tot += total_vector_bytes; + tot += total_vector_slots * word_size; tot += total_floats * sizeof (struct Lisp_Float); tot += total_intervals * sizeof (struct interval); tot += total_strings * sizeof (struct Lisp_String); @@ -5670,27 +5680,40 @@ See Info node `(elisp)Garbage Collection'. */) bounded_number (total_markers), bounded_number (total_free_markers)); - total[3] = list5 (Qstring, make_number (sizeof (struct Lisp_String)), + total[3] = list4 (Qstring, make_number (sizeof (struct Lisp_String)), bounded_number (total_strings), - bounded_number (total_string_bytes), bounded_number (total_free_strings)); - total[4] = list5 (Qvector, make_number (sizeof (struct Lisp_Vector)), - bounded_number (total_vectors), - bounded_number (total_vector_bytes), - bounded_number (total_free_vector_bytes)); + total[4] = list3 (Qstring_bytes, make_number (1), + bounded_number (total_string_bytes)); + + total[5] = list3 (Qvector, make_number (sizeof (struct Lisp_Vector)), + bounded_number (total_vectors)); - total[5] = list4 (Qfloat, make_number (sizeof (struct Lisp_Float)), + total[6] = list4 (Qvector_slots, make_number (word_size), + bounded_number (total_vector_slots), + bounded_number (total_free_vector_slots)); + + total[7] = list4 (Qfloat, make_number (sizeof (struct Lisp_Float)), bounded_number (total_floats), bounded_number (total_free_floats)); - total[6] = list4 (Qinterval, make_number (sizeof (struct interval)), + total[8] = list4 (Qinterval, make_number (sizeof (struct interval)), bounded_number (total_intervals), bounded_number (total_free_intervals)); - total[7] = list3 (Qbuffer, make_number (sizeof (struct buffer)), + total[9] = list3 (Qbuffer, make_number (sizeof (struct buffer)), bounded_number (total_buffers)); + total[10] = list4 (Qheap, make_number (1024), +#ifdef DOUG_LEA_MALLOC + bounded_number ((mallinfo ().uordblks + 1023) >> 10), + bounded_number ((mallinfo ().fordblks + 1023) >> 10) +#else + Qnil, Qnil +#endif + ); + #if GC_MARK_STACK == GC_USE_GCPROS_CHECK_ZOMBIES { /* Compute average percentage of zombies. */ @@ -5987,9 +6010,10 @@ mark_object (Lisp_Object arg) #ifdef GC_CHECK_MARKED_OBJECTS if (po != &buffer_defaults && po != &buffer_local_symbols) { - struct buffer *b = all_buffers; - for (; b && b != po; b = b->header.next.buffer) - ; + struct buffer *b; + FOR_EACH_BUFFER (b) + if (b == po) + break; if (b == NULL) abort (); } @@ -6637,33 +6661,6 @@ We divide the value by 1024 to make sure it fits in a Lisp integer. */) return end; } -DEFUN ("memory-free", Fmemory_free, Smemory_free, 0, 0, 0, - doc: /* Return a list (E H) of two measures of free memory. -E counts free lists maintained by Emacs itself. H counts the heap, -freed by Emacs but not released to the operating system; this is zero -if heap statistics are not available. Both counters are in units of -1024 bytes, rounded up. */) - (void) -{ - /* Make the return value first, so that its storage is accounted for. */ - Lisp_Object val = Fmake_list (make_number (2), make_number (0)); - - XSETCAR (val, - bounded_number - ((total_free_conses * sizeof (struct Lisp_Cons) - + total_free_markers * sizeof (union Lisp_Misc) - + total_free_symbols * sizeof (struct Lisp_Symbol) - + total_free_floats * sizeof (struct Lisp_Float) - + total_free_intervals * sizeof (struct interval) - + total_free_strings * sizeof (struct Lisp_String) - + total_free_vector_bytes - + 1023) >> 10)); -#ifdef DOUG_LEA_MALLOC - XSETCAR (XCDR (val), bounded_number ((mallinfo ().fordblks + 1023) >> 10)); -#endif - return val; -} - DEFUN ("memory-use-counts", Fmemory_use_counts, Smemory_use_counts, 0, 0, 0, doc: /* Return a list of counters that measure how much consing there has been. Each of these counters increments for a certain kind of object. @@ -6679,18 +6676,15 @@ Frames, windows, buffers, and subprocesses count as vectors (but the contents of a buffer's text do not count here). */) (void) { - Lisp_Object consed[8]; - - consed[0] = bounded_number (cons_cells_consed); - consed[1] = bounded_number (floats_consed); - consed[2] = bounded_number (vector_cells_consed); - consed[3] = bounded_number (symbols_consed); - consed[4] = bounded_number (string_chars_consed); - consed[5] = bounded_number (misc_objects_consed); - consed[6] = bounded_number (intervals_consed); - consed[7] = bounded_number (strings_consed); - - return Flist (8, consed); + return listn (CONSTYPE_HEAP, 8, + bounded_number (cons_cells_consed), + bounded_number (floats_consed), + bounded_number (vector_cells_consed), + bounded_number (symbols_consed), + bounded_number (string_chars_consed), + bounded_number (misc_objects_consed), + bounded_number (intervals_consed), + bounded_number (strings_consed)); } /* Find at most FIND_MAX symbols which have OBJ as their value or @@ -6784,7 +6778,7 @@ init_alloc_once (void) #endif refill_memory_reserve (); - gc_cons_threshold = 100000 * sizeof (Lisp_Object); + gc_cons_threshold = GC_DEFAULT_THRESHOLD; } void @@ -6871,13 +6865,17 @@ do hash-consing of the objects allocated to pure space. */); /* We build this in advance because if we wait until we need it, we might not be able to allocate the memory to hold it. */ Vmemory_signal_data - = pure_cons (Qerror, - pure_cons (build_pure_c_string ("Memory exhausted--use M-x save-some-buffers then exit and restart Emacs"), Qnil)); + = listn (CONSTYPE_PURE, 2, Qerror, + build_pure_c_string ("Memory exhausted--use M-x save-some-buffers then exit and restart Emacs")); DEFVAR_LISP ("memory-full", Vmemory_full, doc: /* Non-nil means Emacs cannot get much more Lisp memory. */); Vmemory_full = Qnil; + DEFSYM (Qstring_bytes, "string-bytes"); + DEFSYM (Qvector_slots, "vector-slots"); + DEFSYM (Qheap, "heap"); + DEFSYM (Qgc_cons_threshold, "gc-cons-threshold"); DEFSYM (Qchar_table_extra_slots, "char-table-extra-slots"); @@ -6900,10 +6898,48 @@ The time is in seconds as a floating point value. */); defsubr (&Spurecopy); defsubr (&Sgarbage_collect); defsubr (&Smemory_limit); - defsubr (&Smemory_free); defsubr (&Smemory_use_counts); #if GC_MARK_STACK == GC_USE_GCPROS_CHECK_ZOMBIES defsubr (&Sgc_status); #endif } + +/* Make some symbols visible to GDB. This section is last, so that + the #undef lines don't mess up later code. */ + +/* When compiled with GCC, GDB might say "No enum type named + pvec_type" if we don't have at least one symbol with that type, and + then xbacktrace could fail. Similarly for the other enums and + their values. */ +union +{ + enum CHECK_LISP_OBJECT_TYPE CHECK_LISP_OBJECT_TYPE; + enum enum_USE_LSB_TAG enum_USE_LSB_TAG; + enum Lisp_Bits Lisp_Bits; + enum More_Lisp_Bits More_Lisp_Bits; + enum pvec_type pvec_type; +} const EXTERNALLY_VISIBLE gdb_make_enums_visible = {0}; + +/* These symbols cannot be done as enums, since values might not be + in 'int' range. Each symbol X has a corresponding X_VAL symbol, + verified to have the correct value. */ + +#define ARRAY_MARK_FLAG_VAL PTRDIFF_MIN +#define PSEUDOVECTOR_FLAG_VAL (PTRDIFF_MAX - PTRDIFF_MAX / 2) +#define VALMASK_VAL (USE_LSB_TAG ? -1 << GCTYPEBITS : VAL_MAX) + +verify (ARRAY_MARK_FLAG_VAL == ARRAY_MARK_FLAG); +verify (PSEUDOVECTOR_FLAG_VAL == PSEUDOVECTOR_FLAG); +verify (VALMASK_VAL == VALMASK); + +#undef ARRAY_MARK_FLAG +#undef PSEUDOVECTOR_FLAG +#undef VALMASK + +ptrdiff_t const EXTERNALLY_VISIBLE + ARRAY_MARK_FLAG = ARRAY_MARK_FLAG_VAL, + PSEUDOVECTOR_FLAG = PSEUDOVECTOR_FLAG_VAL; + +EMACS_INT const EXTERNALLY_VISIBLE + VALMASK = VALMASK_VAL;