source: branches/reconfiguration/modules/vci_mem_cache/caba/source/include/mem_cache_directory.h @ 1011

Last change on this file since 1011 was 1011, checked in by cfuguet, 9 years ago

reconf: introduce a scratchpad mode in the memory cache.

  • Initialize the memory cache directory with all slots valid. The cache lines correspond to the lowest local memory segment (i.e. the first 256 Kbytes).
  • If there is a read or write miss while in scratchpad mode, the request is dropped (black-hole behavior).
  • In scratchpad mode, when a broadcast invalidate is triggered by a write, the line is not invalidated. The Read FSM needs to check in the IVT if there is a pending invalidate during a read. The same for the Cleanup FSM. This additional IVT check is only performed when in scratchpad mode.
  • TODO: Support of the scratchpad mode on the CAS FSM. But probably not needed because the distributed bootloader initializes the Local, Remote and Dirty flags to 1 before enabling the MMU.
File size: 25.0 KB
Line 
1#ifndef SOCLIB_CABA_MEM_CACHE_DIRECTORY_H
2#define SOCLIB_CABA_MEM_CACHE_DIRECTORY_H
3
4#include <inttypes.h>
5#include <systemc>
6#include <cassert>
7#include <cstring>
8#include "arithmetics.h"
9
10//#define RANDOM_EVICTION
11
12namespace soclib { namespace caba {
13
14using namespace sc_core;
15
16////////////////////////////////////////////////////////////////////////
17//                    A LRU entry
18////////////////////////////////////////////////////////////////////////
19class LruEntry {
20
21    public:
22
23        bool recent;           
24
25        void init()
26        {
27            recent = false;
28        }
29
30}; // end class LruEntry
31
32////////////////////////////////////////////////////////////////////////
33//                    An Owner
34////////////////////////////////////////////////////////////////////////
35class Owner{
36
37    public:
38        // Fields
39        bool   inst;  // Is the owner an ICache ?
40        size_t srcid; // The SRCID of the owner
41
42        ////////////////////////
43        // Constructors
44        ////////////////////////
45        Owner(bool i_inst,
46                size_t i_srcid)
47        {
48            inst  = i_inst;
49            srcid = i_srcid;
50        }
51
52        Owner(const Owner &a)
53        {
54            inst  = a.inst;
55            srcid = a.srcid;
56        }
57
58        Owner()
59        {
60            inst  = false;
61            srcid = 0;
62        }
63        // end constructors
64
65}; // end class Owner
66
67
68////////////////////////////////////////////////////////////////////////
69//                    A directory entry                               
70////////////////////////////////////////////////////////////////////////
71class DirectoryEntry {
72
73    typedef uint32_t tag_t;
74
75    public:
76
77    bool    valid;  // entry valid
78    bool    is_cnt; // directory entry is in counter mode
79    bool    dirty;  // entry dirty
80    bool    lock;   // entry locked
81    tag_t   tag;    // tag of the entry
82    size_t  count;  // number of copies
83    Owner   owner;  // an owner of the line
84    size_t  ptr;    // pointer to the next owner
85
86    DirectoryEntry()
87    {
88        valid       = false;
89        is_cnt      = false;
90        dirty       = false;
91        lock        = false;
92        tag         = 0;
93        count       = 0;
94        owner.inst  = 0;
95        owner.srcid = 0;
96        ptr         = 0;
97    }
98
99    DirectoryEntry(const DirectoryEntry &source)
100    {
101        valid  = source.valid;
102        is_cnt = source.is_cnt;
103        dirty  = source.dirty;
104        lock   = source.lock;
105        tag    = source.tag;
106        count  = source.count;
107        owner  = source.owner;
108        ptr    = source.ptr;
109    }         
110
111    /////////////////////////////////////////////////////////////////////
112    // The init() function initializes the entry
113    /////////////////////////////////////////////////////////////////////
114    void init()
115    {
116        valid  = false;
117        is_cnt = false;
118        dirty  = false;
119        lock   = false;
120        count  = 0;
121    }
122
123    /////////////////////////////////////////////////////////////////////
124    // The copy() function copies an existing source entry to a target
125    /////////////////////////////////////////////////////////////////////
126    void copy(const DirectoryEntry &source)
127    {
128        valid  = source.valid;
129        is_cnt = source.is_cnt;
130        dirty  = source.dirty;
131        lock   = source.lock;
132        tag    = source.tag;
133        count  = source.count;
134        owner  = source.owner;
135        ptr    = source.ptr;
136    }
137
138    ////////////////////////////////////////////////////////////////////
139    // The print() function prints the entry
140    ////////////////////////////////////////////////////////////////////
141    void print()
142    {
143        std::cout << "Valid = " << valid
144            << " ; IS COUNT = " << is_cnt
145            << " ; Dirty = " << dirty
146            << " ; Lock = " << lock
147            << " ; Tag = " << std::hex << tag << std::dec
148            << " ; Count = " << count
149            << " ; Owner = " << owner.srcid
150            << " " << owner.inst
151            << " ; Pointer = " << ptr << std::endl;
152    }
153
154}; // end class DirectoryEntry
155
156////////////////////////////////////////////////////////////////////////
157//                       The directory 
158////////////////////////////////////////////////////////////////////////
159class CacheDirectory {
160
161    typedef sc_dt::sc_uint<40> addr_t;
162    typedef uint32_t data_t;
163    typedef uint32_t tag_t;
164
165    private:
166
167    // Directory constants
168    size_t   m_ways;
169    size_t   m_sets;
170    size_t   m_words;
171    size_t   m_width;
172    uint32_t lfsr;
173
174    // the directory & lru tables
175    DirectoryEntry ** m_dir_tab;
176    LruEntry       ** m_lru_tab;
177
178    public:
179
180    ////////////////////////
181    // Constructor
182    ////////////////////////
183    CacheDirectory( size_t ways, size_t sets, size_t words, size_t address_width)     
184    {
185        m_ways  = ways; 
186        m_sets  = sets;
187        m_words = words;
188        m_width = address_width;
189        lfsr = -1;
190
191        m_dir_tab = new DirectoryEntry*[sets];
192        for (size_t i = 0; i < sets; i++ ) {
193            m_dir_tab[i] = new DirectoryEntry[ways];
194            for (size_t j = 0; j < ways; j++) m_dir_tab[i][j].init();
195        }
196        m_lru_tab = new LruEntry*[sets];
197        for (size_t i = 0; i < sets; i++) {
198            m_lru_tab[i] = new LruEntry[ways];
199            for (size_t j = 0; j < ways; j++) m_lru_tab[i][j].init();
200        }
201    } // end constructor
202
203    /////////////////
204    // Destructor
205    /////////////////
206    ~CacheDirectory()
207    {
208        for(size_t i = 0; i < m_sets; i++){
209            delete [] m_dir_tab[i];
210            delete [] m_lru_tab[i];
211        }
212        delete [] m_dir_tab;
213        delete [] m_lru_tab;
214    } // end destructor
215
216    /////////////////////////////////////////////////////////////////////
217    // The read() function reads a directory entry. In case of hit, the
218    // LRU is updated.
219    // Arguments :
220    // - address : the address of the entry
221    // - way : (return argument) the way of the entry in case of hit
222    // The function returns a copy of a (valid or invalid) entry 
223    /////////////////////////////////////////////////////////////////////
224    DirectoryEntry read(const addr_t &address, size_t &way)
225    {
226
227#define L2 soclib::common::uint32_log2
228        const size_t set = (size_t)(address >> (L2(m_words) + 2)) & (m_sets - 1);
229        const tag_t  tag = (tag_t)(address >> (L2(m_sets) + L2(m_words) + 2));
230#undef L2
231
232        bool hit = false;
233        for (size_t i = 0; i < m_ways; i++ ) 
234        {
235            bool equal = (m_dir_tab[set][i].tag == tag);
236            bool valid = m_dir_tab[set][i].valid;
237            hit        = equal && valid;
238            if (hit) 
239            {           
240                way = i;
241                break;
242            } 
243        }
244        if (hit) 
245        {
246            m_lru_tab[set][way].recent = true;
247            return DirectoryEntry(m_dir_tab[set][way]);
248        } 
249        else 
250        {
251            return DirectoryEntry();
252        }
253    } // end read()
254
255    /////////////////////////////////////////////////////////////////////
256    // The inval function invalidate an entry defined by the set and
257    // way arguments.
258    /////////////////////////////////////////////////////////////////////
259    void inval(const size_t &way, const size_t &set)
260    {
261        m_dir_tab[set][way].init();
262    }
263
264    /////////////////////////////////////////////////////////////////////
265    // The read_neutral() function reads a directory entry, without
266    // changing the LRU
267    // Arguments :
268    // - address : the address of the entry
269    // The function returns a copy of a (valid or invalid) entry 
270    /////////////////////////////////////////////////////////////////////
271    DirectoryEntry read_neutral(const addr_t &address, 
272            size_t * ret_way,
273            size_t * ret_set )
274    {
275
276#define L2 soclib::common::uint32_log2
277        size_t set = (size_t) (address >> (L2(m_words) + 2)) & (m_sets - 1);
278        tag_t  tag = (tag_t) (address >> (L2(m_sets) + L2(m_words) + 2));
279#undef L2
280
281        for (size_t way = 0; way < m_ways; way++)
282        {
283            bool equal = (m_dir_tab[set][way].tag == tag);
284            bool valid = m_dir_tab[set][way].valid;
285            if (equal and valid)
286            {
287                *ret_set = set;
288                *ret_way = way; 
289                return DirectoryEntry(m_dir_tab[set][way]);
290            }
291        } 
292        return DirectoryEntry();
293    } // end read_neutral()
294
295    /////////////////////////////////////////////////////////////////////
296    // The write function writes a new entry,
297    // and updates the LRU bits if necessary.
298    // Arguments :
299    // - set : the set of the entry
300    // - way : the way of the entry
301    // - entry : the entry value
302    /////////////////////////////////////////////////////////////////////
303    void write(const size_t &set, 
304               const size_t &way, 
305               const DirectoryEntry &entry)
306    {
307        write_neutral(set, way, entry);
308
309        // update LRU bits
310        bool all_recent = true;
311        for (size_t i = 0; i < m_ways; i++) 
312        {
313            if (i != way) all_recent = m_lru_tab[set][i].recent && all_recent;
314        }
315        if (all_recent) 
316        {
317            for (size_t i = 0; i < m_ways; i++) m_lru_tab[set][i].recent = false;
318        } 
319        else 
320        {
321            m_lru_tab[set][way].recent = true;
322        }
323    } // end write()
324
325    /////////////////////////////////////////////////////////////////////
326    // The write_neutral function writes a new entry,
327    // without changing the LRU.
328    // Arguments :
329    // - set : the set of the entry
330    // - way : the way of the entry
331    // - entry : the entry value
332    /////////////////////////////////////////////////////////////////////
333    void write_neutral(const size_t &set,
334                       const size_t &way,
335                       const DirectoryEntry &entry)
336    {
337        assert((set < m_sets) && "Cache Directory write : The set index is invalid");
338        assert((way < m_ways) && "Cache Directory write : The way index is invalid");
339
340        // update Directory
341        m_dir_tab[set][way].copy(entry);
342    } // end write_neutral()
343
344    /////////////////////////////////////////////////////////////////////
345    // The print() function prints a selected directory entry
346    // Arguments :
347    // - set : the set of the entry to print
348    // - way : the way of the entry to print
349    /////////////////////////////////////////////////////////////////////
350    void print(const size_t &set, const size_t &way)
351    {
352        std::cout << std::dec << " set : " << set << " ; way : " << way << " ; " ;
353        m_dir_tab[set][way].print();
354    } // end print()
355
356    /////////////////////////////////////////////////////////////////////
357    // The select() function selects a directory entry to evince.
358    // Arguments :
359    // - set   : (input argument) the set to modify
360    // - way   : (return argument) the way to evince
361    /////////////////////////////////////////////////////////////////////
362    DirectoryEntry select(const size_t &set, size_t &way)
363    {
364        assert((set < m_sets) 
365                && "Cache Directory : (select) The set index is invalid");
366
367        // looking for an empty slot
368        for (size_t i = 0; i < m_ways; i++)
369        {
370            if (not m_dir_tab[set][i].valid)
371            {
372                way = i;
373                return DirectoryEntry(m_dir_tab[set][way]);
374            }
375        }
376
377#ifdef RANDOM_EVICTION
378        lfsr = (lfsr >> 1) ^ ((-(lfsr & 1)) & 0xd0000001);
379        way = lfsr % m_ways;
380        return DirectoryEntry(m_dir_tab[set][way]);
381#endif
382
383        // looking for a not locked and not recently used entry
384        for (size_t i = 0; i < m_ways; i++)
385        {
386            if ((not m_lru_tab[set][i].recent) && (not m_dir_tab[set][i].lock))
387            {
388                way = i;
389                return DirectoryEntry(m_dir_tab[set][way]);
390            }
391        }
392
393        // looking for a locked not recently used entry
394        for (size_t i = 0; i < m_ways; i++)
395        {
396            if ((not m_lru_tab[set][i].recent) && (m_dir_tab[set][i].lock))
397            {
398                way = i;
399                return DirectoryEntry(m_dir_tab[set][way]);
400            }
401        }
402
403        // looking for a recently used entry not locked
404        for (size_t i = 0; i < m_ways; i++)
405        {
406            if ((m_lru_tab[set][i].recent) && (not m_dir_tab[set][i].lock))
407            {
408                way = i;
409                return DirectoryEntry(m_dir_tab[set][way]);
410            }
411        }
412
413        // select way 0 (even if entry is locked and recently used)
414        way = 0;
415        return DirectoryEntry(m_dir_tab[set][0]);
416    } // end select()
417
418    /////////////////////////////////////////////////////////////////////
419    //         Global initialisation function
420    /////////////////////////////////////////////////////////////////////
421    void init()
422    {
423        for (size_t set = 0; set < m_sets; set++) 
424        {
425            for (size_t way = 0; way < m_ways; way++) 
426            {
427                m_dir_tab[set][way].init();
428                m_lru_tab[set][way].init();
429            }
430        }
431    } // end init()
432
433}; // end class CacheDirectory
434
435///////////////////////////////////////////////////////////////////////
436//                    A Heap Entry
437///////////////////////////////////////////////////////////////////////
438class HeapEntry{
439
440    public:
441        // Fields of the entry
442        Owner  owner;
443        size_t next;
444
445        ////////////////////////
446        // Constructor
447        ////////////////////////
448        HeapEntry()
449            :owner(false, 0)
450        {
451            next = 0;
452        } // end constructor
453
454        ////////////////////////
455        // Constructor
456        ////////////////////////
457        HeapEntry(const HeapEntry &entry)
458        {
459            owner.inst  = entry.owner.inst;
460            owner.srcid = entry.owner.srcid;
461            next        = entry.next;
462        } // end constructor
463
464        /////////////////////////////////////////////////////////////////////
465        // The copy() function copies an existing source entry to a target
466        /////////////////////////////////////////////////////////////////////
467        void copy(const HeapEntry &entry)
468        {
469            owner.inst     = entry.owner.inst;
470            owner.srcid    = entry.owner.srcid;
471            next           = entry.next;
472        } // end copy()
473
474        ////////////////////////////////////////////////////////////////////
475        // The print() function prints the entry
476        ////////////////////////////////////////////////////////////////////
477        void print() {
478            std::cout
479                << " -- owner.inst     : " << std::dec << owner.inst << std::endl
480                << " -- owner.srcid    : " << std::dec << owner.srcid << std::endl
481                << " -- next           : " << std::dec << next << std::endl;
482
483        } // end print()
484
485}; // end class HeapEntry
486
487////////////////////////////////////////////////////////////////////////
488//                        The Heap
489////////////////////////////////////////////////////////////////////////
490class HeapDirectory{
491
492    private:
493        // Registers and the heap
494        size_t ptr_free;
495        bool   full;
496        HeapEntry * m_heap_tab;
497
498        // Constants for debugging purpose
499        size_t    tab_size;
500
501    public:
502        ////////////////////////
503        // Constructor
504        ////////////////////////
505        HeapDirectory(uint32_t size) {
506            assert(size > 0 && "Memory Cache, HeapDirectory constructor : invalid size");
507            ptr_free    = 0;
508            full        = false;
509            m_heap_tab  = new HeapEntry[size];
510            tab_size    = size;
511        } // end constructor
512
513        /////////////////
514        // Destructor
515        /////////////////
516        ~HeapDirectory() {
517            delete [] m_heap_tab;
518        } // end destructor
519
520        /////////////////////////////////////////////////////////////////////
521        //         Global initialisation function
522        /////////////////////////////////////////////////////////////////////
523        void init() {
524            ptr_free = 0;
525            full = false;
526            for (size_t i = 0; i< tab_size - 1; i++){
527                m_heap_tab[i].next = i + 1;
528            }
529            m_heap_tab[tab_size - 1].next = tab_size - 1;
530            return;
531        }
532
533        /////////////////////////////////////////////////////////////////////
534        // The print() function prints a selected directory entry
535        // Arguments :
536        // - ptr : the pointer to the entry to print
537        /////////////////////////////////////////////////////////////////////
538        void print(const size_t &ptr) {
539            std::cout << "Heap, printing the entry : " << std::dec << ptr << std::endl;
540            m_heap_tab[ptr].print();
541        } // end print()
542
543        /////////////////////////////////////////////////////////////////////
544        // The print_list() function prints a list from selected directory entry
545        // Arguments :
546        // - ptr : the pointer to the first entry to print
547        /////////////////////////////////////////////////////////////////////
548        void print_list(const size_t &ptr) {
549            bool end = false;
550            size_t ptr_temp = ptr;
551            std::cout << "Heap, printing the list from : " << std::dec << ptr << std::endl;
552            while (!end){
553                m_heap_tab[ptr_temp].print();
554                if (ptr_temp == m_heap_tab[ptr_temp].next) {
555                    end = true;
556                }
557                ptr_temp = m_heap_tab[ptr_temp].next;
558            } 
559        } // end print_list()
560
561        /////////////////////////////////////////////////////////////////////
562        // The is_full() function return true if the heap is full.
563        /////////////////////////////////////////////////////////////////////
564        bool is_full() {
565            return full;
566        } // end is_full()
567
568        /////////////////////////////////////////////////////////////////////
569        // The next_free_ptr() function returns the pointer
570        // to the next free entry.
571        /////////////////////////////////////////////////////////////////////
572        size_t next_free_ptr() {
573            return ptr_free;
574        } // end next_free_ptr()
575
576        /////////////////////////////////////////////////////////////////////
577        // The next_free_entry() function returns
578        // a copy of the next free entry.
579        /////////////////////////////////////////////////////////////////////
580        HeapEntry next_free_entry() {
581            return HeapEntry(m_heap_tab[ptr_free]);
582        } // end next_free_entry()
583
584        /////////////////////////////////////////////////////////////////////
585        // The write_free_entry() function modify the next free entry.
586        // Arguments :
587        // - entry : the entry to write
588        /////////////////////////////////////////////////////////////////////
589        void write_free_entry(const HeapEntry &entry){
590            m_heap_tab[ptr_free].copy(entry);
591        } // end write_free_entry()
592
593        /////////////////////////////////////////////////////////////////////
594        // The write_free_ptr() function writes the pointer
595        // to the next free entry
596        /////////////////////////////////////////////////////////////////////
597        void write_free_ptr(const size_t &ptr){
598            assert((ptr < tab_size) && "HeapDirectory error : try to write a wrong free pointer");
599            ptr_free = ptr;
600        } // end write_free_ptr()
601
602        /////////////////////////////////////////////////////////////////////
603        // The set_full() function sets the full bit (to true).
604        /////////////////////////////////////////////////////////////////////
605        void set_full() {
606            full = true;
607        } // end set_full()
608
609        /////////////////////////////////////////////////////////////////////
610        // The unset_full() function unsets the full bit (to false).
611        /////////////////////////////////////////////////////////////////////
612        void unset_full() {
613            full = false;
614        } // end unset_full()
615
616        /////////////////////////////////////////////////////////////////////
617        // The read() function returns a copy of
618        // the entry pointed by the argument
619        // Arguments :
620        //  - ptr : the pointer to the entry to read
621        /////////////////////////////////////////////////////////////////////
622        HeapEntry read(const size_t &ptr) {
623            assert((ptr < tab_size) && "HeapDirectory error : try to write a wrong free pointer");
624            return HeapEntry(m_heap_tab[ptr]);
625        } // end read()
626
627        /////////////////////////////////////////////////////////////////////
628        // The write() function writes an entry in the heap
629        // Arguments :
630        //  - ptr : the pointer to the entry to replace
631        //  - entry : the entry to write
632        /////////////////////////////////////////////////////////////////////
633        void write(const size_t &ptr, const HeapEntry &entry) {
634            assert((ptr < tab_size) && "HeapDirectory error : try to write a wrong free pointer");
635            m_heap_tab[ptr].copy(entry);
636        } // end write()
637
638}; // end class HeapDirectory
639
640////////////////////////////////////////////////////////////////////////
641//                        Cache Data
642////////////////////////////////////////////////////////////////////////
643class CacheData
644{
645    private:
646        const uint32_t m_sets;
647        const uint32_t m_ways;
648        const uint32_t m_words;
649
650        uint32_t *** m_cache_data;
651
652    public:
653
654        ///////////////////////////////////////////////////////
655        CacheData(uint32_t ways, uint32_t sets, uint32_t words)
656            : m_sets(sets), m_ways(ways), m_words(words) 
657        {
658            m_cache_data = new uint32_t ** [ways];
659            for (size_t i = 0; i < ways; i++) 
660            {
661                m_cache_data[i] = new uint32_t * [sets];
662            }
663            for (size_t i = 0; i < ways; i++) 
664            {
665                for (size_t j = 0; j < sets; j++) 
666                {
667                    m_cache_data[i][j] = new uint32_t[words];
668                    // Init to avoid potential errors from memory checkers
669                    std::memset(m_cache_data[i][j], 0, sizeof(uint32_t) * words);
670                }
671            }
672        }
673        ////////////
674        ~CacheData() 
675        {
676            for (size_t i = 0; i < m_ways; i++)
677            {
678                for (size_t j = 0; j < m_sets; j++)
679                {
680                    delete [] m_cache_data[i][j];
681                }
682            }
683            for (size_t i = 0; i < m_ways; i++)
684            {
685                delete [] m_cache_data[i];
686            }
687            delete [] m_cache_data;
688        }
689        //////////////////////////////////////////
690        uint32_t read (const uint32_t &way,
691                const uint32_t &set,
692                const uint32_t &word) const 
693        {
694            assert((set  < m_sets)  && "Cache data error: Trying to read a wrong set" );
695            assert((way  < m_ways)  && "Cache data error: Trying to read a wrong way" );
696            assert((word < m_words) && "Cache data error: Trying to read a wrong word");
697
698            return m_cache_data[way][set][word];
699        }
700        //////////////////////////////////////////
701        void read_line(const uint32_t &way,
702                const uint32_t &set,
703                sc_core::sc_signal<uint32_t> * cache_line)
704        {
705            assert((set < m_sets) && "Cache data error: Trying to read a wrong set" );
706            assert((way < m_ways) && "Cache data error: Trying to read a wrong way" );
707
708            for (uint32_t word = 0; word < m_words; word++) {
709                cache_line[word].write(m_cache_data[way][set][word]);
710            }
711        }
712        /////////////////////////////////////////
713        void write (const uint32_t &way,
714                const uint32_t &set,
715                const uint32_t &word,
716                const uint32_t &data,
717                const uint32_t &be = 0xF) 
718        {
719
720            assert((set  < m_sets)  && "Cache data error: Trying to write a wrong set" );
721            assert((way  < m_ways)  && "Cache data error: Trying to write a wrong way" );
722            assert((word < m_words) && "Cache data error: Trying to write a wrong word");
723            assert((be  <= 0xF)     && "Cache data error: Trying to write a wrong be");
724
725            if (be == 0x0) return;
726
727            if (be == 0xF)
728            {
729                m_cache_data[way][set][word] = data; 
730                return;
731            }
732
733            uint32_t mask = 0;
734            if (be & 0x1) mask = mask | 0x000000FF;
735            if (be & 0x2) mask = mask | 0x0000FF00;
736            if (be & 0x4) mask = mask | 0x00FF0000;
737            if (be & 0x8) mask = mask | 0xFF000000;
738
739            m_cache_data[way][set][word] = 
740                (data & mask) | (m_cache_data[way][set][word] & ~mask);
741        }
742}; // end class CacheData
743
744}} // end namespaces
745
746#endif
747
748// Local Variables:
749// tab-width: 4
750// c-basic-offset: 4
751// c-file-offsets:((innamespace . 0)(inline-open . 0))
752// indent-tabs-mode: nil
753// End:
754
755// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4
756
Note: See TracBrowser for help on using the repository browser.