- Timestamp:
- Feb 1, 2015 5:12:37 PM (10 years ago)
- Location:
- trunk/src/kmk
- Files:
-
- 7 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/kmk/expand.c
r2769 r2770 256 256 v->expand_count++; 257 257 if ( v->expandprog 258 || (v->expand_count == 10&& kmk_cc_compile_variable_for_expand (v)) )258 || (v->expand_count == 3 && kmk_cc_compile_variable_for_expand (v)) ) 259 259 o = kmk_exec_expand_to_var_buf (v, o); 260 260 else … … 1081 1081 v->value_length = p - v->value; 1082 1082 v->value_alloc_len = variable_buffer_length; 1083 VARIABLE_CHANGED(v); 1083 1084 1084 1085 /* Restore the variable buffer, but without freeing the current. */ -
trunk/src/kmk/function.c
r2768 r2770 1360 1360 var->value[len] = '\0'; 1361 1361 var->value_length = len; 1362 VARIABLE_CHANGED (var); 1362 1363 1363 1364 variable_expand_string_2 (o, body, body_len, &o); … … 1754 1755 while ((p = find_next_token (&t, &len)) != 0) 1755 1756 { 1756 ++t; 1757 if (*t != '\0') /* bird: Fixes access beyond end of string and overflowing words array. */ 1758 ++t; 1757 1759 p[len] = '\0'; 1758 1760 words[wordi++] = p; … … 2160 2162 v->value_length = dst - v->value; 2161 2163 } 2164 2165 VARIABLE_CHANGED (v); 2162 2166 2163 2167 # ifdef CONFIG_WITH_COMPILER … … 4648 4652 stack_var->value_length = lastitem - stack_var->value; 4649 4653 #endif 4654 VARIABLE_CHANGED (stack_var); 4650 4655 } 4651 4656 } -
trunk/src/kmk/kbuild.c
r2540 r2770 570 570 } 571 571 pVar->recursive = 0; 572 VARIABLE_CHANGED(pVar); 572 573 return pVar; 573 574 } … … 2621 2622 pDefTemplate->value_length = off; 2622 2623 pDefTemplate->value[off] = '\0'; 2624 2625 VARIABLE_CHANGED(pDefTemplate); 2623 2626 } 2624 2627 -
trunk/src/kmk/kmk_cc_exec.c
r2769 r2770 70 70 typedef struct KMKCCEXPSTATS 71 71 { 72 /** Max expanded size. */73 uint32_t cchMax;74 72 /** Recent average size. */ 75 73 uint32_t cchAvg; … … 198 196 /** Number of arguments. */ 199 197 uint32_t cArgs; 198 /** Set if the function could be modifying the input arguments. */ 199 uint8_t fDirty; 200 200 /** Where to continue after this instruction. (This is necessary since the 201 201 * instructions are of variable size and may be followed by string data.) */ … … 209 209 * @param pszFuncName The name of the function being called. 210 210 */ 211 char * 211 char * (*pfnFunction)(char *pchDst, char **papszArgs, const char *pszFuncName); 212 212 /** Pointer to the function name in the variable string cache. */ 213 213 const char *pszFuncName; … … 295 295 296 296 /******************************************************************************* 297 * Defined Constants And Macros * 298 *******************************************************************************/ 299 #ifndef NDEBUG 300 # ifdef _MSC_VER 301 # define KMK_CC_ASSERT(a_TrueExpr) do { if (!(a_TrueExpr)) __debugbreak(); } while (0) 302 # else 303 # define KMK_CC_ASSERT(a_TrueExpr) assert(a_TrueExpr) 304 # endif 305 #else 306 # define KMK_CC_ASSERT(a_TrueExpr) do {} while (0) 307 #endif 308 #define KMK_CC_ASSERT_ALIGNED(a_uValue, a_uAlignment) \ 309 KMK_CC_ASSERT( ((a_uValue) & ((a_uAlignment) - 1)) == 0 ) 310 311 312 /******************************************************************************* 297 313 * Global Variables * 298 314 *******************************************************************************/ … … 327 343 PKMKCCBLOCK pNewBlock; 328 344 329 assert(((cbFirst + sizeof(void *) - 1) & (sizeof(void *) - 1)) == 0);345 KMK_CC_ASSERT_ALIGNED(cbFirst, sizeof(void *)); 330 346 331 347 /* … … 377 393 { 378 394 pBlockTail->offNext = (pBlockTail->offNext + sizeof(void *) - 1) & ~(sizeof(void *) - 1); 379 assert(pBlockTail->cbBlock - pBlockTail->offNext >= sizeof(KMKCCEXPJUMP));395 KMK_CC_ASSERT(pBlockTail->cbBlock - pBlockTail->offNext >= sizeof(KMKCCEXPJUMP)); 380 396 } 381 397 } … … 441 457 uint32_t cbLeft = pBlockTail->cbBlock - pBlockTail->offNext; 442 458 443 assert(cbLeft >= sizeof(KMKCCEXPJUMP));459 KMK_CC_ASSERT(cbLeft >= sizeof(KMKCCEXPJUMP)); 444 460 if (cbLeft >= cb + sizeof(KMKCCEXPJUMP)) 445 461 { … … 508 524 pJump->pNext = pRet; 509 525 pOldBlock->offNext += sizeof(*pJump); 510 assert(pOldBlock->offNext <= pOldBlock->cbBlock);526 KMK_CC_ASSERT(pOldBlock->offNext <= pOldBlock->cbBlock); 511 527 512 528 return pRet; … … 526 542 uint32_t cbLeft = pBlockTail->cbBlock - pBlockTail->offNext; 527 543 528 assert(cbLeft >= sizeof(KMKCCEXPJUMP));529 assert(((cb + sizeof(void *) - 1) & (sizeof(void *) - 1)) == 0 || cb == sizeof(KMKCCEXPCORE));544 KMK_CC_ASSERT(cbLeft >= sizeof(KMKCCEXPJUMP)); 545 KMK_CC_ASSERT( (cb & (sizeof(void *) - 1)) == 0 || cb == sizeof(KMKCCEXPCORE) /* final */ ); 530 546 531 547 if (cbLeft >= cb + sizeof(KMKCCEXPJUMP)) … … 588 604 pCore->enmOpCode = kKmkCcExpInstr_Return; 589 605 return 0; 606 } 607 608 609 /** 610 * Checks if a function is known to mess up the arguments its given. 611 * 612 * When executing calls to "dirty" functions, all arguments must be duplicated 613 * on the heap. 614 * 615 * @returns 1 if dirty, 0 if clean. 616 * @param pszFunction The function name. 617 */ 618 static uint8_t kmk_cc_is_dirty_function(const char *pszFunction) 619 { 620 switch (pszFunction[0]) 621 { 622 default: 623 return 0; 624 case 'f': 625 if (pszFunction[1] == 'i') 626 { 627 if (!strcmp(pszFunction, "filter")) 628 return 1; 629 if (!strcmp(pszFunction, "filter-out")) 630 return 1; 631 } 632 return 0; 633 case 's': 634 if (!strcmp(pszFunction, "sort")) 635 return 1; 636 return 0; 637 } 590 638 } 591 639 … … 624 672 pInstr->Core.pfnFunction = pfnFunction; 625 673 pInstr->Core.pszFuncName = pszFunction; 674 pInstr->Core.fDirty = kmk_cc_is_dirty_function(pszFunction); 626 675 627 676 /* … … 645 694 if (ch == chClose) 646 695 { 647 assert(cDepth > 0);696 KMK_CC_ASSERT(cDepth > 0); 648 697 if (cDepth > 0) 649 698 cDepth--; … … 651 700 else if (ch == chOpen) 652 701 cDepth++; 653 else if (ch == ',' && cDepth == 0 )702 else if (ch == ',' && cDepth == 0 && iArg + 1 < cActualArgs) 654 703 break; 655 704 else if (ch == '$') … … 658 707 } 659 708 660 pInstr->aArgs[iArg].fPlain = fDollar;709 pInstr->aArgs[iArg].fPlain = !fDollar; 661 710 if (fDollar) 662 711 { … … 679 728 cchArgs -= cchThisArg + 1; 680 729 } 681 assert(iArg == cActualArgs);730 KMK_CC_ASSERT(iArg == cActualArgs); 682 731 683 732 /* … … 723 772 pInstr->Core.pfnFunction = pfnFunction; 724 773 pInstr->Core.pszFuncName = pszFunction; 774 pInstr->Core.fDirty = kmk_cc_is_dirty_function(pszFunction); 725 775 726 776 /* … … 742 792 if (ch == chClose) 743 793 { 744 assert(cDepth > 0);794 KMK_CC_ASSERT(cDepth > 0); 745 795 if (cDepth > 0) 746 796 cDepth--; … … 748 798 else if (ch == chOpen) 749 799 cDepth++; 750 else if (ch == ',' && cDepth == 0 )800 else if (ch == ',' && cDepth == 0 && iArg + 1 < cActualArgs) 751 801 break; 752 802 cchThisArg++; … … 761 811 } 762 812 763 assert(iArg == cActualArgs);813 KMK_CC_ASSERT(iArg == cActualArgs); 764 814 pInstr->apszArgs[iArg] = NULL; 765 815 … … 787 837 PKMKCCEXPDYNVAR pInstr; 788 838 int rc; 789 assert(cchNameExpr > 0);839 KMK_CC_ASSERT(cchNameExpr > 0); 790 840 791 841 pInstr = (PKMKCCEXPDYNVAR)kmk_cc_block_alloc_exp(ppBlockTail, sizeof(*pInstr)); … … 1002 1052 1003 1053 /* First loop: Identify potential function calls and dynamic expansion. */ 1004 assert(!func_char_map[chOpen]); assert(!func_char_map[chClose]); assert(!func_char_map['$']); 1054 KMK_CC_ASSERT(!func_char_map[chOpen]); 1055 KMK_CC_ASSERT(!func_char_map[chClose]); 1056 KMK_CC_ASSERT(!func_char_map['$']); 1005 1057 while (cchName < cchStr) 1006 1058 { … … 1198 1250 { 1199 1251 pStats->cchAvg = 0; 1200 pStats->cchMax = 0;1201 1252 } 1202 1253 … … 1219 1270 static int kmk_cc_exp_compile_subprog(PKMKCCBLOCK *ppBlockTail, const char *pchStr, uint32_t cchStr, PKMKCCEXPSUBPROG pSubProg) 1220 1271 { 1221 assert(cchStr);1272 KMK_CC_ASSERT(cchStr > 0); 1222 1273 pSubProg->pFirstInstr = (PKMKCCEXPCORE)kmk_cc_block_get_next_ptr(*ppBlockTail); 1223 1274 kmk_cc_exp_stats_init(&pSubProg->Stats); … … 1286 1337 struct kmk_cc_expandprog *kmk_cc_compile_variable_for_expand(struct variable *pVar) 1287 1338 { 1288 assert(!pVar->expandprog);1339 KMK_CC_ASSERT(!pVar->expandprog); 1289 1340 if ( !pVar->expandprog 1290 1341 && pVar->value_length > 0 1291 1342 && pVar->recursive) 1292 1343 { 1293 assert(strlen(pVar->value) == pVar->value_length); 1294 #if 0 /** @todo test & debug this code. Write interpreters! */ 1344 KMK_CC_ASSERT(strlen(pVar->value) == pVar->value_length); 1295 1345 pVar->expandprog = kmk_cc_exp_compile(pVar->value, pVar->value_length); 1296 #endif1297 1346 } 1298 1347 return pVar->expandprog; … … 1475 1524 { 1476 1525 PKMKCCEXPPLAINFUNC pInstr = (PKMKCCEXPPLAINFUNC)pInstrCore; 1526 uint32_t iArg; 1527 if (!pInstr->Core.fDirty) 1528 { 1477 1529 #ifndef NDEBUG 1478 uint32_t uCrcBefore = 0;1479 uint32_t uCrcAfter = 0;1480 uint32_tiArg = pInstr->Core.cArgs;1481 while (iArg-- > 0)1482 uCrcBefore = kmk_exec_debug_string_hash(uCrcBefore, pInstr->apszArgs[iArg]);1530 uint32_t uCrcBefore = 0; 1531 uint32_t uCrcAfter = 0; 1532 iArg = pInstr->Core.cArgs; 1533 while (iArg-- > 0) 1534 uCrcBefore = kmk_exec_debug_string_hash(uCrcBefore, pInstr->apszArgs[iArg]); 1483 1535 #endif 1484 1536 1485 pchDst = pInstr->Core.pfnFunction(pchDst, (char **)&pInstr->apszArgs[0], pInstr->Core.pszFuncName);1537 pchDst = pInstr->Core.pfnFunction(pchDst, (char **)&pInstr->apszArgs[0], pInstr->Core.pszFuncName); 1486 1538 1487 1539 #ifndef NDEBUG 1488 iArg = pInstr->Core.cArgs;1489 while (iArg-- > 0)1490 uCrcAfter = kmk_exec_debug_string_hash(uCrcAfter, pInstr->apszArgs[iArg]);1491 assert(uCrcBefore == uCrcAfter);1540 iArg = pInstr->Core.cArgs; 1541 while (iArg-- > 0) 1542 uCrcAfter = kmk_exec_debug_string_hash(uCrcAfter, pInstr->apszArgs[iArg]); 1543 KMK_CC_ASSERT(uCrcBefore == uCrcAfter); 1492 1544 #endif 1545 } 1546 else 1547 { 1548 char **papszShadowArgs = xmalloc((pInstr->Core.cArgs * 2 + 1) * sizeof(papszShadowArgs[0])); 1549 char **papszArgs = &papszShadowArgs[pInstr->Core.cArgs]; 1550 1551 iArg = pInstr->Core.cArgs; 1552 papszArgs[iArg] = NULL; 1553 while (iArg-- > 0) 1554 papszArgs[iArg] = papszShadowArgs[iArg] = xstrdup(pInstr->apszArgs[iArg]); 1555 1556 pchDst = pInstr->Core.pfnFunction(pchDst, (char **)&pInstr->apszArgs[0], pInstr->Core.pszFuncName); 1557 1558 iArg = pInstr->Core.cArgs; 1559 while (iArg-- > 0) 1560 free(papszShadowArgs[iArg]); 1561 free(papszShadowArgs); 1562 } 1493 1563 1494 1564 pInstrCore = pInstr->Core.pNext; … … 1502 1572 char **papszArgs = &papszArgsShadow[pInstr->Core.cArgs]; 1503 1573 uint32_t iArg; 1504 #ifndef NDEBUG 1505 uint32_t uCrcBefore = 0; 1506 uint32_t uCrcAfter = 0; 1507 #endif 1508 iArg = pInstr->Core.cArgs; 1509 papszArgs[iArg] = NULL; 1510 while (iArg-- > 0) 1511 { 1512 char *pszArg; 1513 if (pInstr->aArgs[iArg].fPlain) 1514 pszArg = (char *)pInstr->aArgs[iArg].u.Plain.pszArg; 1515 else 1516 pszArg = kmk_exec_expand_subprog_to_tmp(&pInstr->aArgs[iArg].u.SubProg, NULL); 1517 papszArgsShadow[iArg] = pszArg; 1518 papszArgs[iArg] = pszArg; 1519 #ifndef NDEBUG 1520 uCrcBefore = kmk_exec_debug_string_hash(uCrcBefore, pszArg); 1521 #endif 1522 } 1523 1524 pchDst = pInstr->Core.pfnFunction(pchDst, papszArgs, pInstr->Core.pszFuncName); 1525 1526 iArg = pInstr->Core.cArgs; 1527 while (iArg-- > 0) 1574 1575 if (!pInstr->Core.fDirty) 1528 1576 { 1529 1577 #ifndef NDEBUG 1530 assert(papszArgsShadow[iArg] == papszArgs[iArg]);1531 u CrcAfter = kmk_exec_debug_string_hash(uCrcAfter, papszArgsShadow[iArg]);1578 uint32_t uCrcBefore = 0; 1579 uint32_t uCrcAfter = 0; 1532 1580 #endif 1533 if (!pInstr->aArgs[iArg].fPlain) 1534 free(papszArgsShadow); 1581 iArg = pInstr->Core.cArgs; 1582 papszArgs[iArg] = NULL; 1583 while (iArg-- > 0) 1584 { 1585 char *pszArg; 1586 if (!pInstr->aArgs[iArg].fPlain) 1587 pszArg = kmk_exec_expand_subprog_to_tmp(&pInstr->aArgs[iArg].u.SubProg, NULL); 1588 else 1589 pszArg = (char *)pInstr->aArgs[iArg].u.Plain.pszArg; 1590 papszArgsShadow[iArg] = pszArg; 1591 papszArgs[iArg] = pszArg; 1592 #ifndef NDEBUG 1593 uCrcBefore = kmk_exec_debug_string_hash(uCrcBefore, pszArg); 1594 #endif 1595 } 1596 pchDst = pInstr->Core.pfnFunction(pchDst, papszArgs, pInstr->Core.pszFuncName); 1597 1598 iArg = pInstr->Core.cArgs; 1599 while (iArg-- > 0) 1600 { 1601 #ifndef NDEBUG 1602 KMK_CC_ASSERT(papszArgsShadow[iArg] == papszArgs[iArg]); 1603 uCrcAfter = kmk_exec_debug_string_hash(uCrcAfter, papszArgsShadow[iArg]); 1604 #endif 1605 if (!pInstr->aArgs[iArg].fPlain) 1606 free(papszArgsShadow[iArg]); 1607 } 1608 KMK_CC_ASSERT(uCrcBefore == uCrcAfter); 1535 1609 } 1536 assert(uCrcBefore == uCrcAfter); 1610 else 1611 { 1612 iArg = pInstr->Core.cArgs; 1613 papszArgs[iArg] = NULL; 1614 while (iArg-- > 0) 1615 { 1616 char *pszArg; 1617 if (!pInstr->aArgs[iArg].fPlain) 1618 pszArg = kmk_exec_expand_subprog_to_tmp(&pInstr->aArgs[iArg].u.SubProg, NULL); 1619 else 1620 pszArg = xstrdup(pInstr->aArgs[iArg].u.Plain.pszArg); 1621 papszArgsShadow[iArg] = pszArg; 1622 papszArgs[iArg] = pszArg; 1623 } 1624 1625 pchDst = pInstr->Core.pfnFunction(pchDst, papszArgs, pInstr->Core.pszFuncName); 1626 1627 iArg = pInstr->Core.cArgs; 1628 while (iArg-- > 0) 1629 free(papszArgsShadow[iArg]); 1630 } 1537 1631 free(papszArgsShadow); 1538 1632 … … 1569 1663 { 1570 1664 /* 1571 * Keep statistics on output size. The average is simplified and not an1572 * ex act average for every expansion that has taken place.1665 * The average is simplified and not an exact average for every 1666 * expansion that has taken place. 1573 1667 */ 1574 if (cchResult > pStats->cchMax)1575 {1576 if (pStats->cchMax)1577 pStats->cchAvg = cchResult;1578 pStats->cchMax = cchResult;1579 }1580 1668 pStats->cchAvg = (pStats->cchAvg * 7 + cchResult) / 8; 1581 1669 } … … 1616 1704 kmk_cc_exp_stats_update(&pSubProg->Stats, cchResult); 1617 1705 1618 restore_variable_buffer(pchOldVarBuf, cbOldVarBuf); 1706 variable_buffer = pchOldVarBuf; 1707 variable_buffer_length = cbOldVarBuf; 1619 1708 1620 1709 return pszResult; … … 1641 1730 1642 1731 cchResult = (uint32_t)(pchDst - variable_buffer); 1643 assert(cchResult >= offStart);1732 KMK_CC_ASSERT(cchResult >= offStart); 1644 1733 cchResult -= offStart; 1645 1734 kmk_cc_exp_stats_update(&pProg->Stats, cchResult); … … 1656 1745 void kmk_exec_evalval(struct variable *pVar) 1657 1746 { 1658 assert(pVar->evalprog);1747 KMK_CC_ASSERT(pVar->evalprog); 1659 1748 assert(0); 1660 1749 } … … 1670 1759 char *kmk_exec_expand_to_var_buf(struct variable *pVar, char *pchDst) 1671 1760 { 1672 assert(pVar->expandprog);1761 KMK_CC_ASSERT(pVar->expandprog); 1673 1762 return kmk_exec_expand_prog_to_var_buf(pVar->expandprog, pchDst); 1674 1763 } … … 1682 1771 void kmk_cc_variable_changed(struct variable *pVar) 1683 1772 { 1684 assert(pVar->evalprog || pVar->expandprog);1773 KMK_CC_ASSERT(pVar->evalprog || pVar->expandprog); 1685 1774 #if 0 1686 1775 if (pVar->evalprog) … … 1705 1794 void kmk_cc_variable_deleted(struct variable *pVar) 1706 1795 { 1707 assert(pVar->evalprog || pVar->expandprog);1796 KMK_CC_ASSERT(pVar->evalprog || pVar->expandprog); 1708 1797 #if 0 1709 1798 if (pVar->evalprog) -
trunk/src/kmk/read.c
r2717 r2770 2296 2296 2297 2297 v->origin = origin; 2298 #ifndef CONFIG_WITH_VALUE_LENGTH 2298 2299 if (v->flavor == f_simple) 2299 2300 v->value = allocated_variable_expand (v->value); 2300 2301 else 2301 2302 v->value = xstrdup (v->value); 2303 #else 2304 v->value_length = strlen (v->value); 2305 if (v->flavor == f_simple) 2306 v->value = allocated_variable_expand_2 (v->value, v->value_length, &v->value_length); 2307 else 2308 v->value = (char *)memcpy (xmalloc (v->value_length + 1), v->value, v->value_length + 1); 2309 v->value_alloc_len = v->value_length + 1; 2310 #endif 2302 2311 2303 2312 fname = p->target; … … 2370 2379 v->recursive = gv->recursive; 2371 2380 v->append = 0; 2381 VARIABLE_CHANGED (v); 2372 2382 } 2373 2383 } -
trunk/src/kmk/variable.c
r2769 r2770 386 386 v->origin = origin; 387 387 v->recursive = recursive; 388 MAKE_STATS_2(v->changes++); 389 #ifdef CONFIG_WITH_COMPILER 390 if (v->evalprog || v->expandprog) 391 kmk_cc_variable_changed (v); 392 #endif 388 VARIABLE_CHANGED (v); 393 389 } 394 390 return v; … … 603 599 if (v->value != 0 && !v->rdonly_val) 604 600 free (v->value); 605 MAKE_STATS_2(v->changes++); 606 #ifdef CONFIG_WITH_COMPILER 607 if (v->evalprog || v->expandprog) 608 kmk_cc_variable_changed (v); 609 #endif 601 VARIABLE_CHANGED (v); 610 602 } 611 603 else … … 764 756 var->value_alloc_len = max; 765 757 #endif 758 VARIABLE_CHANGED (var); 766 759 767 760 /* Remember how many variables are in our current count. Since we never … … 2225 2218 memcpy (v->value, value, value_len + 1); 2226 2219 v->value_length = new_value_len; 2220 VARIABLE_CHANGED (v); 2227 2221 } 2228 2222 … … 2439 2433 if (free_value) 2440 2434 free (free_value); 2441 MAKE_STATS_2(v->changes++);2442 # ifdef CONFIG_WITH_COMPILER2443 if (v->evalprog || v->expandprog)2444 kmk_cc_variable_changed (v);2445 # endif2446 2435 return v; 2447 2436 #else /* !CONFIG_WITH_VALUE_LENGTH */ -
trunk/src/kmk/variable.h
r2769 r2770 116 116 #endif 117 117 #if defined (CONFIG_WITH_COMPILER) || defined (CONFIG_WITH_MAKE_STATS) 118 unsigned int evalval_count; /* Times used with $(evalval ) or $(evalctx ) . */119 unsigned int expand_count; /* Times expanded (not to be confused with exp_count). */118 unsigned int evalval_count; /* Times used with $(evalval ) or $(evalctx ) since last change. */ 119 unsigned int expand_count; /* Times expanded since last change (not to be confused with exp_count). */ 120 120 #endif 121 121 #ifdef CONFIG_WITH_COMPILER … … 124 124 #endif 125 125 }; 126 127 /* Update statistics and invalidates optimizations when a variable changes. */ 128 #ifdef CONFIG_WITH_COMPILER 129 # define VARIABLE_CHANGED(v) \ 130 do { \ 131 MAKE_STATS_2((v)->changes++); \ 132 if ((v)->evalprog || (v)->expandprog) kmk_cc_variable_changed(v); \ 133 (v)->expand_count = 0; \ 134 (v)->evalval_count = 0; \ 135 } while (0) 136 #else 137 # define VARIABLE_CHANGED(v) MAKE_STATS_2((v)->changes++) 138 #endif 139 140 126 141 127 142 /* Structure that represents a variable set. */
Note:
See TracChangeset
for help on using the changeset viewer.