cache.cc (11194:c3ba89c653a9) | cache.cc (11197:f8fdd931e674) |
---|---|
1/* 2 * Copyright (c) 2010-2015 ARM Limited 3 * All rights reserved. 4 * 5 * The license below extends only to copyright in the software and shall 6 * not be construed as granting a license to any other intellectual 7 * property including but not limited to intellectual property relating 8 * to a hardware implementation of the functionality of the software --- 54 unchanged lines hidden (view full) --- 63#include "mem/cache/prefetch/base.hh" 64#include "sim/sim_exit.hh" 65 66Cache::Cache(const CacheParams *p) 67 : BaseCache(p, p->system->cacheLineSize()), 68 tags(p->tags), 69 prefetcher(p->prefetcher), 70 doFastWrites(true), | 1/* 2 * Copyright (c) 2010-2015 ARM Limited 3 * All rights reserved. 4 * 5 * The license below extends only to copyright in the software and shall 6 * not be construed as granting a license to any other intellectual 7 * property including but not limited to intellectual property relating 8 * to a hardware implementation of the functionality of the software --- 54 unchanged lines hidden (view full) --- 63#include "mem/cache/prefetch/base.hh" 64#include "sim/sim_exit.hh" 65 66Cache::Cache(const CacheParams *p) 67 : BaseCache(p, p->system->cacheLineSize()), 68 tags(p->tags), 69 prefetcher(p->prefetcher), 70 doFastWrites(true), |
71 prefetchOnAccess(p->prefetch_on_access) | 71 prefetchOnAccess(p->prefetch_on_access), 72 clusivity(p->clusivity), 73 tempBlockWriteback(nullptr), 74 writebackTempBlockAtomicEvent(this, false, 75 EventBase::Delayed_Writeback_Pri) |
72{ 73 tempBlock = new CacheBlk(); 74 tempBlock->data = new uint8_t[blkSize]; 75 76 cpuSidePort = new CpuSidePort(p->name + ".cpu_side", this, 77 "CpuSidePort"); 78 memSidePort = new MemSidePort(p->name + ".mem_side", this, 79 "MemSidePort"); --- 113 unchanged lines hidden (view full) --- 193 assert(pkt->cmd == MemCmd::ReadExReq || 194 pkt->cmd == MemCmd::SCUpgradeFailReq); 195 196 // if we have a dirty copy, make sure the recipient 197 // keeps it marked dirty 198 if (blk->isDirty()) { 199 pkt->assertMemInhibit(); 200 } | 76{ 77 tempBlock = new CacheBlk(); 78 tempBlock->data = new uint8_t[blkSize]; 79 80 cpuSidePort = new CpuSidePort(p->name + ".cpu_side", this, 81 "CpuSidePort"); 82 memSidePort = new MemSidePort(p->name + ".mem_side", this, 83 "MemSidePort"); --- 113 unchanged lines hidden (view full) --- 197 assert(pkt->cmd == MemCmd::ReadExReq || 198 pkt->cmd == MemCmd::SCUpgradeFailReq); 199 200 // if we have a dirty copy, make sure the recipient 201 // keeps it marked dirty 202 if (blk->isDirty()) { 203 pkt->assertMemInhibit(); 204 } |
201 // on ReadExReq we give up our copy unconditionally 202 if (blk != tempBlock) 203 tags->invalidate(blk); 204 blk->invalidate(); | 205 // on ReadExReq we give up our copy unconditionally, 206 // even if this cache is mostly inclusive, we may want 207 // to revisit this 208 invalidateBlock(blk); |
205 } else if (blk->isWritable() && !pending_downgrade && 206 !pkt->sharedAsserted() && 207 pkt->cmd != MemCmd::ReadCleanReq) { 208 // we can give the requester an exclusive copy (by not 209 // asserting shared line) on a read request if: 210 // - we have an exclusive copy at this level (& below) 211 // - we don't have a pending snoop from below 212 // signaling another read request 213 // - no other cache above has a copy (otherwise it 214 // would have asseretd shared line on request) 215 // - we are not satisfying an instruction fetch (this 216 // prevents dirty data in the i-cache) 217 218 if (blk->isDirty()) { 219 // special considerations if we're owner: 220 if (!deferred_response) { 221 // if we are responding immediately and can 222 // signal that we're transferring ownership | 209 } else if (blk->isWritable() && !pending_downgrade && 210 !pkt->sharedAsserted() && 211 pkt->cmd != MemCmd::ReadCleanReq) { 212 // we can give the requester an exclusive copy (by not 213 // asserting shared line) on a read request if: 214 // - we have an exclusive copy at this level (& below) 215 // - we don't have a pending snoop from below 216 // signaling another read request 217 // - no other cache above has a copy (otherwise it 218 // would have asseretd shared line on request) 219 // - we are not satisfying an instruction fetch (this 220 // prevents dirty data in the i-cache) 221 222 if (blk->isDirty()) { 223 // special considerations if we're owner: 224 if (!deferred_response) { 225 // if we are responding immediately and can 226 // signal that we're transferring ownership |
223 // along with exclusivity, do so | 227 // (inhibit set) along with exclusivity 228 // (shared not set), do so |
224 pkt->assertMemInhibit(); | 229 pkt->assertMemInhibit(); |
230 231 // if this cache is mostly inclusive, we keep 232 // the block as writable (exclusive), and pass 233 // it upwards as writable and dirty 234 // (modified), hence we have multiple caches 235 // considering the same block writable, 236 // something that we get away with due to the 237 // fact that: 1) this cache has been 238 // considered the ordering points and 239 // responded to all snoops up till now, and 2) 240 // we always snoop upwards before consulting 241 // the local cache, both on a normal request 242 // (snooping done by the crossbar), and on a 243 // snoop |
|
225 blk->status &= ~BlkDirty; | 244 blk->status &= ~BlkDirty; |
245 246 // if this cache is mostly exclusive with 247 // respect to the cache above, drop the block 248 if (clusivity == Enums::mostly_excl) { 249 invalidateBlock(blk); 250 } |
|
226 } else { 227 // if we're responding after our own miss, 228 // there's a window where the recipient didn't 229 // know it was getting ownership and may not 230 // have responded to snoops correctly, so we 231 // can't pass off ownership *or* exclusivity 232 pkt->assertShared(); 233 } 234 } 235 } else { 236 // otherwise only respond with a shared copy 237 pkt->assertShared(); 238 } 239 } 240 } else { 241 // Upgrade or Invalidate, since we have it Exclusively (E or 242 // M), we ack then invalidate. 243 assert(pkt->isUpgrade() || pkt->isInvalidate()); | 251 } else { 252 // if we're responding after our own miss, 253 // there's a window where the recipient didn't 254 // know it was getting ownership and may not 255 // have responded to snoops correctly, so we 256 // can't pass off ownership *or* exclusivity 257 pkt->assertShared(); 258 } 259 } 260 } else { 261 // otherwise only respond with a shared copy 262 pkt->assertShared(); 263 } 264 } 265 } else { 266 // Upgrade or Invalidate, since we have it Exclusively (E or 267 // M), we ack then invalidate. 268 assert(pkt->isUpgrade() || pkt->isInvalidate()); |
244 assert(blk != tempBlock); 245 tags->invalidate(blk); 246 blk->invalidate(); | 269 270 // for invalidations we could be looking at the temp block 271 // (for upgrades we always allocate) 272 invalidateBlock(blk); |
247 DPRINTF(Cache, "%s for %s addr %#llx size %d (invalidation)\n", 248 __func__, pkt->cmdString(), pkt->getAddr(), pkt->getSize()); 249 } 250} 251 252 253///////////////////////////////////////////////////// 254// --- 501 unchanged lines hidden (view full) --- 756 } 757 // We use forward_time here because it is the same 758 // considering new targets. We have multiple 759 // requests for the same address here. It 760 // specifies the latency to allocate an internal 761 // buffer and to schedule an event to the queued 762 // port and also takes into account the additional 763 // delay of the xbar. | 273 DPRINTF(Cache, "%s for %s addr %#llx size %d (invalidation)\n", 274 __func__, pkt->cmdString(), pkt->getAddr(), pkt->getSize()); 275 } 276} 277 278 279///////////////////////////////////////////////////// 280// --- 501 unchanged lines hidden (view full) --- 782 } 783 // We use forward_time here because it is the same 784 // considering new targets. We have multiple 785 // requests for the same address here. It 786 // specifies the latency to allocate an internal 787 // buffer and to schedule an event to the queued 788 // port and also takes into account the additional 789 // delay of the xbar. |
764 mshr->allocateTarget(pkt, forward_time, order++); | 790 mshr->allocateTarget(pkt, forward_time, order++, 791 allocOnFill(pkt->cmd)); |
765 if (mshr->getNumTargets() == numTarget) { 766 noTargetMSHR = mshr; 767 setBlocked(Blocked_NoTargets); 768 // need to be careful with this... if this mshr isn't 769 // ready yet (i.e. time > curTick()), we don't want to 770 // move it ahead of mshrs that are ready 771 // mshrQueue.moveToFront(mshr); 772 } --- 249 unchanged lines hidden (view full) --- 1022 // an invalidate request 1023 satisfyCpuSideRequest(pkt, blk); 1024 } 1025 } else if (pkt->cmd == MemCmd::WriteLineReq) { 1026 // note the use of pkt, not bus_pkt here. 1027 1028 // write-line request to the cache that promoted 1029 // the write to a whole line | 792 if (mshr->getNumTargets() == numTarget) { 793 noTargetMSHR = mshr; 794 setBlocked(Blocked_NoTargets); 795 // need to be careful with this... if this mshr isn't 796 // ready yet (i.e. time > curTick()), we don't want to 797 // move it ahead of mshrs that are ready 798 // mshrQueue.moveToFront(mshr); 799 } --- 249 unchanged lines hidden (view full) --- 1049 // an invalidate request 1050 satisfyCpuSideRequest(pkt, blk); 1051 } 1052 } else if (pkt->cmd == MemCmd::WriteLineReq) { 1053 // note the use of pkt, not bus_pkt here. 1054 1055 // write-line request to the cache that promoted 1056 // the write to a whole line |
1030 blk = handleFill(pkt, blk, writebacks); | 1057 blk = handleFill(pkt, blk, writebacks, 1058 allocOnFill(pkt->cmd)); |
1031 satisfyCpuSideRequest(pkt, blk); 1032 } else if (bus_pkt->isRead() || 1033 bus_pkt->cmd == MemCmd::UpgradeResp) { 1034 // we're updating cache state to allow us to 1035 // satisfy the upstream request from the cache | 1059 satisfyCpuSideRequest(pkt, blk); 1060 } else if (bus_pkt->isRead() || 1061 bus_pkt->cmd == MemCmd::UpgradeResp) { 1062 // we're updating cache state to allow us to 1063 // satisfy the upstream request from the cache |
1036 blk = handleFill(bus_pkt, blk, writebacks); | 1064 blk = handleFill(bus_pkt, blk, writebacks, 1065 allocOnFill(pkt->cmd)); |
1037 satisfyCpuSideRequest(pkt, blk); 1038 } else { 1039 // we're satisfying the upstream request without 1040 // modifying cache state, e.g., a write-through 1041 pkt->makeAtomicResponse(); 1042 } 1043 } 1044 delete bus_pkt; --- 6 unchanged lines hidden (view full) --- 1051 // rely on bandwidth contention to throttle them; these will tend 1052 // to pollute the cache in atomic mode since there is no bandwidth 1053 // contention. If we ever do want to enable prefetching in atomic 1054 // mode, though, this is the place to do it... see timingAccess() 1055 // for an example (though we'd want to issue the prefetch(es) 1056 // immediately rather than calling requestMemSideBus() as we do 1057 // there). 1058 | 1066 satisfyCpuSideRequest(pkt, blk); 1067 } else { 1068 // we're satisfying the upstream request without 1069 // modifying cache state, e.g., a write-through 1070 pkt->makeAtomicResponse(); 1071 } 1072 } 1073 delete bus_pkt; --- 6 unchanged lines hidden (view full) --- 1080 // rely on bandwidth contention to throttle them; these will tend 1081 // to pollute the cache in atomic mode since there is no bandwidth 1082 // contention. If we ever do want to enable prefetching in atomic 1083 // mode, though, this is the place to do it... see timingAccess() 1084 // for an example (though we'd want to issue the prefetch(es) 1085 // immediately rather than calling requestMemSideBus() as we do 1086 // there). 1087 |
1059 // Handle writebacks (from the response handling) if needed | 1088 // do any writebacks resulting from the response handling |
1060 doWritebacksAtomic(writebacks); 1061 | 1089 doWritebacksAtomic(writebacks); 1090 |
1091 // if we used temp block, check to see if its valid and if so 1092 // clear it out, but only do so after the call to recvAtomic is 1093 // finished so that any downstream observers (such as a snoop 1094 // filter), first see the fill, and only then see the eviction 1095 if (blk == tempBlock && tempBlock->isValid()) { 1096 // the atomic CPU calls recvAtomic for fetch and load/store 1097 // sequentuially, and we may already have a tempBlock 1098 // writeback from the fetch that we have not yet sent 1099 if (tempBlockWriteback) { 1100 // if that is the case, write the prevoius one back, and 1101 // do not schedule any new event 1102 writebackTempBlockAtomic(); 1103 } else { 1104 // the writeback/clean eviction happens after the call to 1105 // recvAtomic has finished (but before any successive 1106 // calls), so that the response handling from the fill is 1107 // allowed to happen first 1108 schedule(writebackTempBlockAtomicEvent, curTick()); 1109 } 1110 1111 tempBlockWriteback = blk->isDirty() ? writebackBlk(blk) : 1112 cleanEvictBlk(blk); 1113 blk->invalidate(); 1114 } 1115 |
|
1062 if (pkt->needsResponse()) { 1063 pkt->makeAtomicResponse(); 1064 } 1065 1066 return lat * clockPeriod(); 1067} 1068 1069 --- 139 unchanged lines hidden (view full) --- 1209 (pkt->isRead() || pkt->cmd == MemCmd::UpgradeResp); 1210 1211 CacheBlk *blk = tags->findBlock(pkt->getAddr(), pkt->isSecure()); 1212 1213 if (is_fill && !is_error) { 1214 DPRINTF(Cache, "Block for addr %#llx being updated in Cache\n", 1215 pkt->getAddr()); 1216 | 1116 if (pkt->needsResponse()) { 1117 pkt->makeAtomicResponse(); 1118 } 1119 1120 return lat * clockPeriod(); 1121} 1122 1123 --- 139 unchanged lines hidden (view full) --- 1263 (pkt->isRead() || pkt->cmd == MemCmd::UpgradeResp); 1264 1265 CacheBlk *blk = tags->findBlock(pkt->getAddr(), pkt->isSecure()); 1266 1267 if (is_fill && !is_error) { 1268 DPRINTF(Cache, "Block for addr %#llx being updated in Cache\n", 1269 pkt->getAddr()); 1270 |
1217 blk = handleFill(pkt, blk, writebacks); | 1271 blk = handleFill(pkt, blk, writebacks, mshr->allocOnFill); |
1218 assert(blk != NULL); 1219 } 1220 1221 // allow invalidation responses originating from write-line 1222 // requests to be discarded 1223 bool is_invalidate = pkt->isInvalidate(); 1224 1225 // First offset for critical word first calculations --- 27 unchanged lines hidden (view full) --- 1253 // state. We "catch up" with that logic here, which is duplicated 1254 // from above. 1255 if (tgt_pkt->cmd == MemCmd::WriteLineReq) { 1256 assert(!is_error); 1257 // we got the block in exclusive state, so promote any 1258 // deferred targets if possible 1259 mshr->promoteExclusive(); 1260 // NB: we use the original packet here and not the response! | 1272 assert(blk != NULL); 1273 } 1274 1275 // allow invalidation responses originating from write-line 1276 // requests to be discarded 1277 bool is_invalidate = pkt->isInvalidate(); 1278 1279 // First offset for critical word first calculations --- 27 unchanged lines hidden (view full) --- 1307 // state. We "catch up" with that logic here, which is duplicated 1308 // from above. 1309 if (tgt_pkt->cmd == MemCmd::WriteLineReq) { 1310 assert(!is_error); 1311 // we got the block in exclusive state, so promote any 1312 // deferred targets if possible 1313 mshr->promoteExclusive(); 1314 // NB: we use the original packet here and not the response! |
1261 blk = handleFill(tgt_pkt, blk, writebacks); | 1315 blk = handleFill(tgt_pkt, blk, writebacks, mshr->allocOnFill); |
1262 assert(blk != NULL); 1263 1264 // treat as a fill, and discard the invalidation 1265 // response 1266 is_fill = true; 1267 is_invalidate = false; 1268 } 1269 --- 87 unchanged lines hidden (view full) --- 1357 mshr->popTarget(); 1358 } 1359 1360 if (blk && blk->isValid()) { 1361 // an invalidate response stemming from a write line request 1362 // should not invalidate the block, so check if the 1363 // invalidation should be discarded 1364 if (is_invalidate || mshr->hasPostInvalidate()) { | 1316 assert(blk != NULL); 1317 1318 // treat as a fill, and discard the invalidation 1319 // response 1320 is_fill = true; 1321 is_invalidate = false; 1322 } 1323 --- 87 unchanged lines hidden (view full) --- 1411 mshr->popTarget(); 1412 } 1413 1414 if (blk && blk->isValid()) { 1415 // an invalidate response stemming from a write line request 1416 // should not invalidate the block, so check if the 1417 // invalidation should be discarded 1418 if (is_invalidate || mshr->hasPostInvalidate()) { |
1365 assert(blk != tempBlock); 1366 tags->invalidate(blk); 1367 blk->invalidate(); | 1419 invalidateBlock(blk); |
1368 } else if (mshr->hasPostDowngrade()) { 1369 blk->status &= ~BlkWritable; 1370 } 1371 } 1372 1373 if (mshr->promoteDeferredTargets()) { 1374 // avoid later read getting stale data while write miss is 1375 // outstanding.. see comment in timingAccess() --- 207 unchanged lines hidden (view full) --- 1583 writebacks.push_back(cleanEvictBlk(blk)); 1584 } 1585 } 1586 } 1587 1588 return blk; 1589} 1590 | 1420 } else if (mshr->hasPostDowngrade()) { 1421 blk->status &= ~BlkWritable; 1422 } 1423 } 1424 1425 if (mshr->promoteDeferredTargets()) { 1426 // avoid later read getting stale data while write miss is 1427 // outstanding.. see comment in timingAccess() --- 207 unchanged lines hidden (view full) --- 1635 writebacks.push_back(cleanEvictBlk(blk)); 1636 } 1637 } 1638 } 1639 1640 return blk; 1641} 1642 |
1643void 1644Cache::invalidateBlock(CacheBlk *blk) 1645{ 1646 if (blk != tempBlock) 1647 tags->invalidate(blk); 1648 blk->invalidate(); 1649} |
|
1591 1592// Note that the reason we return a list of writebacks rather than 1593// inserting them directly in the write buffer is that this function 1594// is called by both atomic and timing-mode accesses, and in atomic 1595// mode we don't mess with the write buffer (we just perform the 1596// writebacks atomically once the original request is complete). 1597CacheBlk* | 1650 1651// Note that the reason we return a list of writebacks rather than 1652// inserting them directly in the write buffer is that this function 1653// is called by both atomic and timing-mode accesses, and in atomic 1654// mode we don't mess with the write buffer (we just perform the 1655// writebacks atomically once the original request is complete). 1656CacheBlk* |
1598Cache::handleFill(PacketPtr pkt, CacheBlk *blk, PacketList &writebacks) | 1657Cache::handleFill(PacketPtr pkt, CacheBlk *blk, PacketList &writebacks, 1658 bool allocate) |
1599{ 1600 assert(pkt->isResponse() || pkt->cmd == MemCmd::WriteLineReq); 1601 Addr addr = pkt->getAddr(); 1602 bool is_secure = pkt->isSecure(); 1603#if TRACING_ON 1604 CacheBlk::State old_state = blk ? blk->status : 0; 1605#endif 1606 --- 7 unchanged lines hidden (view full) --- 1614 // better have read new data... 1615 assert(pkt->hasData()); 1616 1617 // only read responses and write-line requests have data; 1618 // note that we don't write the data here for write-line - that 1619 // happens in the subsequent satisfyCpuSideRequest. 1620 assert(pkt->isRead() || pkt->cmd == MemCmd::WriteLineReq); 1621 | 1659{ 1660 assert(pkt->isResponse() || pkt->cmd == MemCmd::WriteLineReq); 1661 Addr addr = pkt->getAddr(); 1662 bool is_secure = pkt->isSecure(); 1663#if TRACING_ON 1664 CacheBlk::State old_state = blk ? blk->status : 0; 1665#endif 1666 --- 7 unchanged lines hidden (view full) --- 1674 // better have read new data... 1675 assert(pkt->hasData()); 1676 1677 // only read responses and write-line requests have data; 1678 // note that we don't write the data here for write-line - that 1679 // happens in the subsequent satisfyCpuSideRequest. 1680 assert(pkt->isRead() || pkt->cmd == MemCmd::WriteLineReq); 1681 |
1622 // need to do a replacement 1623 blk = allocateBlock(addr, is_secure, writebacks); | 1682 // need to do a replacement if allocating, otherwise we stick 1683 // with the temporary storage 1684 blk = allocate ? allocateBlock(addr, is_secure, writebacks) : NULL; 1685 |
1624 if (blk == NULL) { | 1686 if (blk == NULL) { |
1625 // No replaceable block... just use temporary storage to 1626 // complete the current request and then get rid of it | 1687 // No replaceable block or a mostly exclusive 1688 // cache... just use temporary storage to complete the 1689 // current request and then get rid of it |
1627 assert(!tempBlock->isValid()); 1628 blk = tempBlock; 1629 tempBlock->set = tags->extractSet(addr); 1630 tempBlock->tag = tags->extractTag(addr); 1631 // @todo: set security state as well... 1632 DPRINTF(Cache, "using temp block for %#llx (%s)\n", addr, 1633 is_secure ? "s" : "ns"); 1634 } else { --- 237 unchanged lines hidden (view full) --- 1872 1873 if (respond) { 1874 // prevent anyone else from responding, cache as well as 1875 // memory, and also prevent any memory from even seeing the 1876 // request (with current inhibited semantics), note that this 1877 // applies both to reads and writes and that for writes it 1878 // works thanks to the fact that we still have dirty data and 1879 // will write it back at a later point | 1690 assert(!tempBlock->isValid()); 1691 blk = tempBlock; 1692 tempBlock->set = tags->extractSet(addr); 1693 tempBlock->tag = tags->extractTag(addr); 1694 // @todo: set security state as well... 1695 DPRINTF(Cache, "using temp block for %#llx (%s)\n", addr, 1696 is_secure ? "s" : "ns"); 1697 } else { --- 237 unchanged lines hidden (view full) --- 1935 1936 if (respond) { 1937 // prevent anyone else from responding, cache as well as 1938 // memory, and also prevent any memory from even seeing the 1939 // request (with current inhibited semantics), note that this 1940 // applies both to reads and writes and that for writes it 1941 // works thanks to the fact that we still have dirty data and 1942 // will write it back at a later point |
1943 assert(!pkt->memInhibitAsserted()); |
|
1880 pkt->assertMemInhibit(); 1881 if (have_exclusive) { 1882 // in the case of an uncacheable request there is no point 1883 // in setting the exclusive flag, but since the recipient 1884 // does not care there is no harm in doing so, in any case 1885 // it is just a hint 1886 pkt->setSupplyExclusive(); 1887 } --- 18 unchanged lines hidden (view full) --- 1906 assert(pkt->needsResponse()); 1907 delete pkt->req; 1908 delete pkt; 1909 } 1910 1911 // Do this last in case it deallocates block data or something 1912 // like that 1913 if (invalidate) { | 1944 pkt->assertMemInhibit(); 1945 if (have_exclusive) { 1946 // in the case of an uncacheable request there is no point 1947 // in setting the exclusive flag, but since the recipient 1948 // does not care there is no harm in doing so, in any case 1949 // it is just a hint 1950 pkt->setSupplyExclusive(); 1951 } --- 18 unchanged lines hidden (view full) --- 1970 assert(pkt->needsResponse()); 1971 delete pkt->req; 1972 delete pkt; 1973 } 1974 1975 // Do this last in case it deallocates block data or something 1976 // like that 1977 if (invalidate) { |
1914 if (blk != tempBlock) 1915 tags->invalidate(blk); 1916 blk->invalidate(); | 1978 invalidateBlock(blk); |
1917 } 1918 1919 DPRINTF(Cache, "new state is %s\n", blk->print()); 1920 1921 return snoop_delay; 1922} 1923 1924 --- 613 unchanged lines hidden --- | 1979 } 1980 1981 DPRINTF(Cache, "new state is %s\n", blk->print()); 1982 1983 return snoop_delay; 1984} 1985 1986 --- 613 unchanged lines hidden --- |