reorganized the tracker to be a bit more modular
--HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%40121
This commit is contained in:
		
							parent
							
								
									6f2ebd8da7
								
							
						
					
					
						commit
						2c65e42379
					
				@ -66,6 +66,7 @@
 | 
				
			|||||||
#include "lstring.h"
 | 
					#include "lstring.h"
 | 
				
			||||||
#include "sc.h"
 | 
					#include "sc.h"
 | 
				
			||||||
#include "svnrev.h"
 | 
					#include "svnrev.h"
 | 
				
			||||||
 | 
					#include "sctracker.h"
 | 
				
			||||||
#define VERSION_STR "3.2." SVN_REVSTR
 | 
					#define VERSION_STR "3.2." SVN_REVSTR
 | 
				
			||||||
#define VERSION_INT 0x0302
 | 
					#define VERSION_INT 0x0302
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1056,13 +1056,13 @@ static int hier14(value *lval1)
 | 
				
			|||||||
 *   (a() ? return_array() : return_array()) ? return_array() : return_array()
 | 
					 *   (a() ? return_array() : return_array()) ? return_array() : return_array()
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void dynarray_from_heaplist(heapuse_list_t *heap)
 | 
					void dynarray_from_heaplist(memuse_list_t *heap)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
  heapuse_t *use=heap->head;
 | 
					  memuse_t *use=heap->head;
 | 
				
			||||||
  heapuse_t *tmp;
 | 
					  memuse_t *tmp;
 | 
				
			||||||
  long total=0;
 | 
					  long total=0;
 | 
				
			||||||
  while (use) {
 | 
					  while (use) {
 | 
				
			||||||
    assert(use->type==HEAPUSE_STATIC);
 | 
					    assert(use->type==MEMUSE_STATIC);
 | 
				
			||||||
    total+=use->size;
 | 
					    total+=use->size;
 | 
				
			||||||
    tmp=use->prev;
 | 
					    tmp=use->prev;
 | 
				
			||||||
    free(use);
 | 
					    free(use);
 | 
				
			||||||
@ -1081,7 +1081,7 @@ static int hier13(value *lval)
 | 
				
			|||||||
    int flab2=getlabel();
 | 
					    int flab2=getlabel();
 | 
				
			||||||
    value lval2={0};
 | 
					    value lval2={0};
 | 
				
			||||||
    int array1,array2;
 | 
					    int array1,array2;
 | 
				
			||||||
    heapuse_list_t *heap;
 | 
					    memuse_list_t *heap;
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    pushheaplist();
 | 
					    pushheaplist();
 | 
				
			||||||
    if (lvalue) {
 | 
					    if (lvalue) {
 | 
				
			||||||
@ -1124,7 +1124,7 @@ static int hier13(value *lval)
 | 
				
			|||||||
    dynarray_from_heaplist(heap);
 | 
					    dynarray_from_heaplist(heap);
 | 
				
			||||||
    setlabel(flab2);
 | 
					    setlabel(flab2);
 | 
				
			||||||
    if (array1 && array2) {
 | 
					    if (array1 && array2) {
 | 
				
			||||||
      markheap(HEAPUSE_DYNAMIC, 0);
 | 
					      markheap(MEMUSE_DYNAMIC, 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (lval->ident==iARRAY)
 | 
					    if (lval->ident==iARRAY)
 | 
				
			||||||
      lval->ident=iREFARRAY;    /* iARRAY becomes iREFARRAY */
 | 
					      lval->ident=iREFARRAY;    /* iARRAY becomes iREFARRAY */
 | 
				
			||||||
@ -1907,7 +1907,7 @@ static void setdefarray(cell *string,cell size,cell array_sz,cell *dataaddr,int
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    assert(array_sz>=size);
 | 
					    assert(array_sz>=size);
 | 
				
			||||||
    modheap((int)array_sz*sizeof(cell));
 | 
					    modheap((int)array_sz*sizeof(cell));
 | 
				
			||||||
    markheap(HEAPUSE_STATIC, array_sz);
 | 
					    markheap(MEMUSE_STATIC, array_sz);
 | 
				
			||||||
    /* ??? should perhaps fill with zeros first */
 | 
					    /* ??? should perhaps fill with zeros first */
 | 
				
			||||||
    memcopy(size*sizeof(cell));
 | 
					    memcopy(size*sizeof(cell));
 | 
				
			||||||
    moveto1();
 | 
					    moveto1();
 | 
				
			||||||
@ -1982,7 +1982,7 @@ static int nesting=0;
 | 
				
			|||||||
    assert(retsize>0);
 | 
					    assert(retsize>0);
 | 
				
			||||||
    modheap(retsize*sizeof(cell));/* address is in ALT */
 | 
					    modheap(retsize*sizeof(cell));/* address is in ALT */
 | 
				
			||||||
    pushreg(sALT);                /* pass ALT as the last (hidden) parameter */
 | 
					    pushreg(sALT);                /* pass ALT as the last (hidden) parameter */
 | 
				
			||||||
    markheap(HEAPUSE_STATIC, retsize);
 | 
					    markheap(MEMUSE_STATIC, retsize);
 | 
				
			||||||
    /* also mark the ident of the result as "array" */
 | 
					    /* also mark the ident of the result as "array" */
 | 
				
			||||||
    lval_result->ident=iREFARRAY;
 | 
					    lval_result->ident=iREFARRAY;
 | 
				
			||||||
    lval_result->sym=symret;
 | 
					    lval_result->sym=symret;
 | 
				
			||||||
@ -2090,14 +2090,14 @@ static int nesting=0;
 | 
				
			|||||||
              } else {
 | 
					              } else {
 | 
				
			||||||
                rvalue(&lval);    /* get value in PRI */
 | 
					                rvalue(&lval);    /* get value in PRI */
 | 
				
			||||||
                setheap_pri();    /* address of the value on the heap in PRI */
 | 
					                setheap_pri();    /* address of the value on the heap in PRI */
 | 
				
			||||||
                heapalloc+=markheap(HEAPUSE_STATIC, 1);
 | 
					                heapalloc+=markheap(MEMUSE_STATIC, 1);
 | 
				
			||||||
                nest_stkusage++;
 | 
					                nest_stkusage++;
 | 
				
			||||||
              } /* if */
 | 
					              } /* if */
 | 
				
			||||||
            } else if (lvalue) {
 | 
					            } else if (lvalue) {
 | 
				
			||||||
              address(lval.sym,sPRI);
 | 
					              address(lval.sym,sPRI);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              setheap_pri();      /* address of the value on the heap in PRI */
 | 
					              setheap_pri();      /* address of the value on the heap in PRI */
 | 
				
			||||||
              heapalloc+=markheap(HEAPUSE_STATIC, 1);
 | 
					              heapalloc+=markheap(MEMUSE_STATIC, 1);
 | 
				
			||||||
              nest_stkusage++;
 | 
					              nest_stkusage++;
 | 
				
			||||||
            } /* if */
 | 
					            } /* if */
 | 
				
			||||||
          } else if (lval.ident==iCONSTEXPR || lval.ident==iEXPRESSION
 | 
					          } else if (lval.ident==iCONSTEXPR || lval.ident==iEXPRESSION
 | 
				
			||||||
@ -2109,7 +2109,7 @@ static int nesting=0;
 | 
				
			|||||||
            /* allocate a cell on the heap and store the
 | 
					            /* allocate a cell on the heap and store the
 | 
				
			||||||
             * value (already in PRI) there */
 | 
					             * value (already in PRI) there */
 | 
				
			||||||
            setheap_pri();        /* address of the value on the heap in PRI */
 | 
					            setheap_pri();        /* address of the value on the heap in PRI */
 | 
				
			||||||
            heapalloc+=markheap(HEAPUSE_STATIC, 1);
 | 
					            heapalloc+=markheap(MEMUSE_STATIC, 1);
 | 
				
			||||||
            nest_stkusage++;
 | 
					            nest_stkusage++;
 | 
				
			||||||
          } /* if */
 | 
					          } /* if */
 | 
				
			||||||
          /* ??? handle const array passed by reference */
 | 
					          /* ??? handle const array passed by reference */
 | 
				
			||||||
@ -2147,7 +2147,7 @@ static int nesting=0;
 | 
				
			|||||||
              address(lval.sym,sPRI);
 | 
					              address(lval.sym,sPRI);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              setheap_pri();      /* address of the value on the heap in PRI */
 | 
					              setheap_pri();      /* address of the value on the heap in PRI */
 | 
				
			||||||
              heapalloc+=markheap(HEAPUSE_STATIC, 1);
 | 
					              heapalloc+=markheap(MEMUSE_STATIC, 1);
 | 
				
			||||||
              nest_stkusage++;
 | 
					              nest_stkusage++;
 | 
				
			||||||
            } /* if */
 | 
					            } /* if */
 | 
				
			||||||
          } /* if */
 | 
					          } /* if */
 | 
				
			||||||
@ -2294,7 +2294,7 @@ static int nesting=0;
 | 
				
			|||||||
      } else if (arg[argidx].ident==iREFERENCE) {
 | 
					      } else if (arg[argidx].ident==iREFERENCE) {
 | 
				
			||||||
        setheap(arg[argidx].defvalue.val);
 | 
					        setheap(arg[argidx].defvalue.val);
 | 
				
			||||||
        /* address of the value on the heap in PRI */
 | 
					        /* address of the value on the heap in PRI */
 | 
				
			||||||
        heapalloc+=markheap(HEAPUSE_STATIC, 1);
 | 
					        heapalloc+=markheap(MEMUSE_STATIC, 1);
 | 
				
			||||||
        nest_stkusage++;
 | 
					        nest_stkusage++;
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        int dummytag=arg[argidx].tags[0];
 | 
					        int dummytag=arg[argidx].tags[0];
 | 
				
			||||||
 | 
				
			|||||||
@ -3,28 +3,104 @@
 | 
				
			|||||||
#include "sc.h"
 | 
					#include "sc.h"
 | 
				
			||||||
#include "sctracker.h"
 | 
					#include "sctracker.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
heapuse_list_t *heapusage = NULL;
 | 
					memuse_list_t *heapusage = NULL;
 | 
				
			||||||
 | 
					memuse_list_t *stackusage = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
* Creates a new heap allocation tracker entry
 | 
					 * Creates a new mem usage tracker entry
 | 
				
			||||||
*/
 | 
					 */
 | 
				
			||||||
void pushheaplist()
 | 
					void _push_memlist(memuse_list_t **head)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	heapuse_list_t *newlist=(heapuse_list_t *)malloc(sizeof(heapuse_list_t));
 | 
					  memuse_list_t *newlist = (memuse_list_t *)malloc(sizeof(memuse_list_t));
 | 
				
			||||||
	newlist->prev=heapusage;
 | 
					  (*head)->prev = *head;
 | 
				
			||||||
	newlist->head=NULL;
 | 
					  (*head)->head = NULL;
 | 
				
			||||||
	heapusage=newlist;
 | 
					  *head = newlist;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
* Generates code to free all heap allocations on a tracker
 | 
					 * Pops a heap list but does not free it.
 | 
				
			||||||
*/
 | 
					 */
 | 
				
			||||||
void freeheapusage(heapuse_list_t *heap)
 | 
					memuse_list_t *_pop_save_memlist(memuse_list_t **head)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	heapuse_t *cur=heap->head;
 | 
					  memuse_list_t *oldlist = *head;
 | 
				
			||||||
	heapuse_t *tmp;
 | 
					  *head = (*head)->prev;
 | 
				
			||||||
 | 
					  return oldlist;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Marks a memory usage on a memory list
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					int _mark_memlist(memuse_list_t *head, int type, int size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  memuse_t *use;
 | 
				
			||||||
 | 
					  if (type==MEMUSE_STATIC && size==0)
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  use=head->head;
 | 
				
			||||||
 | 
					  if (use && (type==MEMUSE_STATIC) 
 | 
				
			||||||
 | 
					      && (use->type == type))
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    use->size += size;
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    use=(memuse_t *)malloc(sizeof(memuse_t));
 | 
				
			||||||
 | 
					    use->type=type;
 | 
				
			||||||
 | 
					    use->size=size;
 | 
				
			||||||
 | 
					    use->prev=head->head;
 | 
				
			||||||
 | 
					    head->head=use;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return size;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Wrapper for pushing the heap list
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void pushheaplist()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  _push_memlist(&heapusage);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Wrapper for popping and saving the heap list
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					memuse_list_t *popsaveheaplist()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  return _pop_save_memlist(&heapusage);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Wrapper for marking the heap
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					int markheap(int type, int size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  return _mark_memlist(heapusage, type, size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Wrapper for pushing the stack list
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void pushstacklist()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  _push_memlist(&stackusage);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Wrapper for marking the stack
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					int markstack(int type, int size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  return _mark_memlist(stackusage, type, size);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Generates code to free all heap allocations on a tracker
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void _heap_freeusage(memuse_list_t *heap)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  memuse_t *cur=heap->head;
 | 
				
			||||||
 | 
					  memuse_t *tmp;
 | 
				
			||||||
  while (cur) {
 | 
					  while (cur) {
 | 
				
			||||||
		if (cur->type == HEAPUSE_STATIC) {
 | 
					    if (cur->type == MEMUSE_STATIC) {
 | 
				
			||||||
      modheap((-1)*cur->size*sizeof(cell));
 | 
					      modheap((-1)*cur->size*sizeof(cell));
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      modheap_i();
 | 
					      modheap_i();
 | 
				
			||||||
@ -37,24 +113,14 @@ void freeheapusage(heapuse_list_t *heap)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
* Pops a heap list but does not free it.
 | 
					 * Pops a heap list and frees it.
 | 
				
			||||||
*/
 | 
					 */
 | 
				
			||||||
heapuse_list_t *popsaveheaplist()
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	heapuse_list_t *oldlist=heapusage;
 | 
					 | 
				
			||||||
	heapusage=heapusage->prev;
 | 
					 | 
				
			||||||
	return oldlist;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
* Pops a heap list and frees it.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
void popheaplist()
 | 
					void popheaplist()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	heapuse_list_t *oldlist;
 | 
					  memuse_list_t *oldlist;
 | 
				
			||||||
  assert(heapusage!=NULL);
 | 
					  assert(heapusage!=NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	freeheapusage(heapusage);
 | 
					  _heap_freeusage(heapusage);
 | 
				
			||||||
  assert(heapusage->head==NULL);
 | 
					  assert(heapusage->head==NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  oldlist=heapusage->prev;
 | 
					  oldlist=heapusage->prev;
 | 
				
			||||||
@ -62,25 +128,4 @@ void popheaplist()
 | 
				
			|||||||
  heapusage=oldlist;
 | 
					  heapusage=oldlist;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					
 | 
				
			||||||
* Returns the size passed in
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
int markheap(int type, int size)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	heapuse_t *use;
 | 
					 | 
				
			||||||
	if (type==HEAPUSE_STATIC && size==0)
 | 
					 | 
				
			||||||
		return 0;
 | 
					 | 
				
			||||||
	use=heapusage->head;
 | 
					 | 
				
			||||||
	if (use && (type==HEAPUSE_STATIC) 
 | 
					 | 
				
			||||||
		&& (use->type == type))
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		use->size += size;
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		use=(heapuse_t *)malloc(sizeof(heapuse_t));
 | 
					 | 
				
			||||||
		use->type=type;
 | 
					 | 
				
			||||||
		use->size=size;
 | 
					 | 
				
			||||||
		use->prev=heapusage->head;
 | 
					 | 
				
			||||||
		heapusage->head=use;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return size;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -1,26 +1,36 @@
 | 
				
			|||||||
#ifndef _INCLUDE_SOURCEPAWN_COMPILER_TRACKER_H_
 | 
					#ifndef _INCLUDE_SOURCEPAWN_COMPILER_TRACKER_H_
 | 
				
			||||||
#define _INCLUDE_SOURCEPAWN_COMPILER_TRACKER_H_
 | 
					#define _INCLUDE_SOURCEPAWN_COMPILER_TRACKER_H_
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define HEAPUSE_STATIC	0
 | 
					#define MEMUSE_STATIC      0
 | 
				
			||||||
#define HEAPUSE_DYNAMIC	1
 | 
					#define MEMUSE_DYNAMIC     1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct heapuse_s {
 | 
					typedef struct memuse_s {
 | 
				
			||||||
	int type;   /* HEAPUSE_STATIC or HEAPUSE_DYNAMIC */
 | 
					  int type;   /* MEMUSE_STATIC or MEMUSE_DYNAMIC */
 | 
				
			||||||
  int size;   /* size of array for static (0 for dynamic) */
 | 
					  int size;   /* size of array for static (0 for dynamic) */
 | 
				
			||||||
	struct heapuse_s *prev; /* previous array on the list */
 | 
					  struct memuse_s *prev; /* previous block on the list */
 | 
				
			||||||
} heapuse_t;
 | 
					} memuse_t;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
typedef struct heapuse_list_s {
 | 
					typedef struct memuse_list_s {
 | 
				
			||||||
	struct heapuse_list_s *prev;   /* last used list */
 | 
					  struct memuse_list_s *prev;   /* last used list */
 | 
				
			||||||
	heapuse_t *head;               /* head of the current list */
 | 
					  memuse_t *head;               /* head of the current list */
 | 
				
			||||||
} heapuse_list_t;
 | 
					} memuse_list_t;
 | 
				
			||||||
 | 
					 | 
				
			||||||
extern heapuse_list_t *heapusage;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Heap functions
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
void pushheaplist();
 | 
					void pushheaplist();
 | 
				
			||||||
void freeheapusage(heapuse_list_t *heap);
 | 
					memuse_list_t *popsaveheaplist();
 | 
				
			||||||
heapuse_list_t *popsaveheaplist();
 | 
					 | 
				
			||||||
void popheaplist();
 | 
					void popheaplist();
 | 
				
			||||||
int markheap(int type, int size);
 | 
					int markheap(int type, int size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Stack functions
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void pushstacklist();
 | 
				
			||||||
 | 
					void popstacklist();
 | 
				
			||||||
 | 
					int markstack(int type, int size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern memuse_list_t *heapusage;
 | 
				
			||||||
 | 
					extern memuse_list_t *stackusage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif //_INCLUDE_SOURCEPAWN_COMPILER_TRACKER_H_
 | 
					#endif //_INCLUDE_SOURCEPAWN_COMPILER_TRACKER_H_
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user