package wax-lib
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
Libraries for Wax, a Rust-like syntax for WebAssembly
Install
dune-project
Dependency
Authors
Maintainers
Sources
wax-0.1.0.tbz
sha256=41b580846af8d41bdf6c3f005f62e38feda3e60fe2e9e4aa440db34ce515a153
sha512=4b3a181fcc7d743194a8647260870fb5190770066a197bcc48104c2b77fd40c643228b795c2bcd6b29a120820e969eb42a37a9bcec98b3f608d13f152d9f6579
doc/src/wax-lib.wasm/validation.ml.html
Source file validation.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 2181 2182 2183 2184 2185 2186 2187 2188 2189 2190 2191 2192 2193 2194 2195 2196 2197 2198 2199 2200 2201 2202 2203 2204 2205 2206 2207 2208 2209 2210 2211 2212 2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224 2225 2226 2227 2228 2229 2230 2231 2232 2233 2234 2235 2236 2237 2238 2239 2240 2241 2242 2243 2244 2245 2246 2247 2248 2249 2250 2251 2252 2253 2254 2255 2256 2257 2258 2259 2260 2261 2262 2263 2264 2265 2266 2267 2268 2269 2270 2271 2272 2273 2274 2275 2276 2277 2278 2279 2280 2281 2282 2283 2284 2285 2286 2287 2288 2289 2290 2291 2292 2293 2294 2295 2296 2297 2298 2299 2300 2301 2302 2303 2304 2305 2306 2307 2308 2309 2310 2311 2312 2313 2314 2315 2316 2317 2318 2319 2320 2321 2322 2323 2324 2325 2326 2327 2328 2329 2330 2331 2332 2333 2334 2335 2336 2337 2338 2339 2340 2341 2342 2343 2344 2345 2346 2347 2348 2349 2350 2351 2352 2353 2354 2355 2356 2357 2358 2359 2360 2361 2362 2363 2364 2365 2366 2367 2368 2369 2370 2371 2372 2373 2374 2375 2376 2377 2378 2379 2380 2381 2382 2383 2384 2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396 2397 2398 2399 2400 2401 2402 2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418 2419 2420 2421 2422 2423 2424 2425 2426 2427 2428 2429 2430 2431 2432 2433 2434 2435 2436 2437 2438 2439 2440 2441 2442 2443 2444 2445 2446 2447 2448 2449 2450 2451 2452 2453 2454 2455 2456 2457 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 2472 2473 2474 2475 2476 2477 2478 2479 2480 2481 2482 2483 2484 2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565 2566 2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577 2578 2579 2580 2581 2582 2583 2584 2585 2586 2587 2588 2589 2590 2591 2592 2593 2594 2595 2596 2597 2598 2599 2600 2601 2602 2603 2604 2605 2606 2607 2608 2609 2610 2611 2612 2613 2614 2615 2616 2617 2618 2619 2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 2966 2967 2968 2969 2970 2971 2972 2973 2974 2975 2976 2977 2978 2979 2980 2981 2982 2983 2984 2985 2986 2987 2988 2989 2990 2991 2992 2993 2994 2995 2996 2997 2998 2999 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 3011 3012 3013 3014 3015 3016 3017 3018 3019 3020 3021 3022 3023 3024 3025 3026 3027 3028 3029 3030 3031 3032 3033 3034 3035 3036 3037 3038 3039 3040 3041 3042 3043 3044 3045 3046 3047 3048 3049 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 3090 3091 3092 3093 3094 3095 3096 3097 3098 3099 3100 3101 3102 3103 3104 3105 3106 3107 3108 3109 3110 3111 3112 3113 3114 3115 3116 3117 3118 3119 3120 3121 3122 3123 3124 3125 3126 3127 3128 3129 3130 3131 3132 3133 3134 3135 3136 3137 3138 3139 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 3160 3161 3162 3163 3164 3165 3166 3167 3168 3169 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 3190 3191 3192 3193 3194 3195 3196 3197 3198 3199 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 3220 3221 3222 3223 3224 3225 3226 3227 3228 3229 3230 3231 3232 3233 3234 3235 3236 3237 3238 3239 3240 3241 3242 3243 3244 3245 3246 3247 3248 3249 3250 3251 3252 3253 3254 3255 3256 3257 3258 3259 3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 3295 3296 3297 3298 3299 3300 3301 3302 3303 3304 3305 3306 3307 3308 3309 3310 3311 3312 3313 3314 3315 3316 3317 3318 3319 3320 3321 3322 3323 3324 3325 3326 3327 3328 3329 3330 3331 3332 3333 3334 3335 3336 3337 3338 3339 3340 3341 3342 3343 3344 3345 3346 3347 3348 3349 3350 3351 3352 3353 3354 3355 3356 3357 3358 3359 3360 3361 3362 3363 3364 3365 3366 3367 3368 3369 3370 3371 3372 3373 3374 3375 3376 3377 3378 3379 3380 3381 3382 3383 3384 3385 3386 3387 3388 3389 3390 3391 3392 3393 3394 3395 3396 3397 3398 3399 3400 3401 3402 3403 3404 3405 3406 3407 3408 3409 3410 3411 3412 3413 3414 3415 3416 3417 3418 3419 3420 3421 3422 3423 3424 3425 3426 3427 3428 3429 3430 3431 3432 3433 3434 3435 3436 3437 3438 3439 3440 3441 3442 3443 3444 3445 3446 3447 3448 3449 3450 3451 3452 3453 3454 3455 3456 3457 3458 3459 3460 3461 3462 3463 3464 3465 3466 3467 3468 3469 3470 3471 3472 3473 3474 3475 3476 3477 3478 3479 3480 3481 3482 3483 3484 3485 3486 3487 3488 3489 3490 3491 3492 3493 3494 3495 3496 3497 3498 3499 3500 3501 3502 3503 3504 3505 3506 3507 3508 3509 3510 3511 3512 3513 3514 3515 3516 3517 3518 3519 3520 3521 3522 3523 3524 3525 3526 3527 3528 3529 3530 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 3546 3547 3548 3549 3550 3551 3552 3553 3554 3555 3556 3557 3558 3559 3560 3561 3562 3563 3564 3565 3566 3567 3568 3569 3570 3571 3572 3573 3574 3575 3576 3577 3578 3579 3580 3581 3582 3583 3584 3585 3586 3587 3588 3589 3590 3591 3592 3593 3594 3595 3596 3597 3598 3599 3600 3601 3602 3603 3604 3605 3606 3607 3608 3609 3610 3611 3612 3613 3614 3615 3616 3617 3618 3619 3620 3621 3622 3623 3624 3625 3626 3627 3628 3629 3630 3631 3632 3633 3634 3635 3636 3637 3638 3639 3640 3641 3642 3643 3644 3645 3646 3647 3648 3649 3650 3651 3652 3653 3654 3655 3656 3657 3658 3659 3660 3661 3662 3663 3664 3665 3666 3667 3668 3669 3670 3671 3672 3673 3674 3675 3676 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 3703 3704 3705 3706 3707 3708 3709 3710 3711 3712 3713 3714 3715 3716 3717 3718 3719 3720 3721 3722 3723 3724 3725 3726 3727 3728 3729 3730 3731 3732 3733 3734 3735 3736 3737 3738 3739 3740 3741 3742 3743 3744 3745 3746 3747 3748 3749 3750 3751 3752 3753 3754 3755 3756 3757 3758 3759 3760 3761 3762 3763 3764 3765 3766 3767 3768 3769 3770 3771 3772 3773 3774 3775 3776 3777 3778 3779 3780 3781 3782 3783 3784 3785 3786 3787 3788 3789 3790 3791 3792 3793 3794 3795 3796 3797 3798 3799 3800 3801 3802 3803 3804 3805 3806 3807 3808 3809 3810 3811 3812 3813 3814 3815 3816 3817 3818 3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 3833 3834 3835 3836 3837 3838 3839 3840 3841 3842 3843 3844 3845 3846 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 3951 3952 3953 3954 3955 3956 3957 3958 3959 3960 3961 3962 3963 3964 3965 3966 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 4011 4012 4013 4014 4015 4016 4017 4018 4019 4020 4021 4022 4023 4024 4025 4026 4027 4028 4029 4030 4031 4032 4033 4034 4035 4036 4037 4038 4039 4040 4041 4042 4043 4044 4045 4046 4047 4048 4049 4050 4051 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 4073 4074 4075 4076 4077 4078 4079 4080 4081 4082 4083 4084 4085 4086 4087 4088 4089 4090 4091 4092 4093 4094 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 4110 4111 4112 4113 4114 4115 4116 4117 4118 4119 4120 4121 4122 4123 4124 4125 4126 4127 4128 4129 4130 4131 4132 4133 4134 4135 4136 4137 4138 4139 4140 4141 4142 4143 4144 4145 4146 4147 4148 4149 4150 4151 4152 4153 4154 4155 4156 4157 4158 4159 4160 4161 4162 4163 4164 4165 4166 4167 4168 4169 4170 4171 4172 4173 4174 4175 4176 4177 4178 4179 4180 4181 4182 4183 4184 4185 4186 4187 4188 4189 4190 4191 4192 4193 4194 4195 4196 4197 4198 4199 4200 4201 4202 4203 4204 4205 4206 4207 4208 4209 4210 4211 4212 4213 4214 4215 4216 4217 4218 4219 4220 4221 4222 4223 4224 4225 4226 4227 4228 4229 4230 4231 4232 4233 4234 4235 4236 4237 4238 4239 4240 4241 4242 4243 4244 4245 4246 4247 4248 4249 4250 4251 4252 4253 4254 4255 4256 4257 4258 4259 4260 4261 4262 4263 4264 4265 4266 4267 4268 4269 4270 4271 4272 4273 4274 4275 4276 4277 4278 4279 4280 4281 4282 4283 4284 4285 4286 4287 4288 4289 4290 4291 4292 4293 4294 4295 4296 4297 4298 4299 4300 4301 4302 4303 4304 4305 4306 4307 4308 4309 4310 4311 4312 4313 4314 4315 4316 4317 4318 4319 4320 4321 4322 4323 4324 4325 4326 4327 4328 4329 4330 4331 4332 4333 4334 4335 4336 4337 4338 4339 4340 4341 4342 4343 4344 4345 4346 4347 4348 4349 4350 4351 4352 4353 4354 4355 4356 4357 4358 4359 4360 4361 4362 4363 4364 4365 4366 4367 4368 4369 4370 4371 4372 4373 4374 4375 4376 4377 4378 4379 4380 4381 4382 4383 4384 4385 4386 4387 4388 4389 4390 4391 4392 4393 4394 4395 4396 4397 4398 4399 4400 4401 4402 4403 4404 4405 4406 4407 4408 4409 4410 4411 4412 4413 4414 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4427 4428 4429 4430 4431 4432 4433 4434 4435 4436 4437 4438 4439 4440 4441 4442 4443 4444 4445 4446 4447 4448 4449 4450 4451 4452 4453 4454 4455 4456 4457 4458 4459 4460 4461 4462 4463 4464 4465 4466 4467 4468 4469 4470 4471 4472 4473 4474 4475 4476 4477 4478 4479 4480 4481 4482 4483 4484 4485 4486 4487 4488 4489 4490 4491 4492 4493 4494 4495 4496 4497 4498 4499 4500 4501 4502 4503 4504 4505 4506 4507 4508 4509 4510 4511 4512 4513 4514 4515 4516 4517 4518 4519 4520 4521 4522 4523 4524 4525 4526 4527 4528 4529 4530 4531 4532 4533 4534 4535 4536 4537 4538 4539 4540 4541 4542 4543 4544 4545 4546 4547 4548 4549 4550 4551 4552 4553 4554 4555 4556 4557 4558 4559 4560 4561 4562 4563 4564 4565 4566 4567 4568 4569 4570 4571 4572 4573 4574 4575 4576 4577 4578 4579 4580 4581 4582 4583 4584 4585 4586 4587 4588 4589 4590 4591 4592 4593 4594 4595 4596 4597 4598 4599 4600 4601 4602 4603 4604 4605 4606 4607 4608 4609 4610 4611 4612 4613 4614 4615 4616 4617 4618 4619 4620 4621 4622 4623 4624 4625 4626 4627 4628 4629 4630 4631 4632 4633 4634 4635 4636 4637 4638 4639 4640 4641 4642 4643 4644 4645 4646 4647 4648 4649 4650 4651 4652 4653 4654 4655 4656 4657 4658 4659 4660 4661 4662 4663 4664 4665 4666 4667 4668 4669 4670 4671 4672 4673 4674 4675 4676 4677 4678 4679 4680 4681 4682 4683 4684 4685 4686 4687 4688 4689 4690 4691 4692 4693 4694 4695 4696 4697 4698 4699 4700 4701 4702 4703 4704 4705 4706 4707 4708 4709 4710 4711 4712 4713 4714 4715 4716 4717 4718 4719 4720 4721 4722 4723 4724 4725 4726 4727 4728 4729 4730 4731 4732 4733 4734 4735 4736 4737 4738 4739 4740 4741 4742 4743 4744 4745 4746 4747 4748 4749 4750 4751 4752 4753 4754 4755 4756 4757 4758 4759 4760 4761 4762 4763 4764 4765 4766 4767 4768 4769 4770 4771 4772 4773 4774 4775 4776 4777 4778 4779 4780 4781 4782 4783 4784 4785 4786 4787 4788 4789 4790 4791 4792 4793 4794 4795 4796 4797 4798 4799 4800 4801 4802 4803 4804 4805 4806 4807 4808 4809 4810 4811 4812 4813 4814 4815 4816 4817 4818 4819 4820 4821 4822 4823 4824 4825 4826 4827 4828 4829 4830 4831 4832 4833 4834 4835 4836 4837 4838 4839 4840 4841 4842 4843 4844 4845 4846 4847 4848 4849 4850 4851 4852 4853 4854 4855 4856 4857 4858 4859 4860 4861 4862 4863 4864 4865 4866 4867 4868 4869 4870 4871 4872 4873 4874 4875 4876 4877 4878 4879 4880 4881 4882 4883 4884 4885 4886 4887 4888 4889 4890 4891 4892 4893 4894 4895 4896 4897 4898 4899 4900 4901 4902 4903 4904 4905 4906 4907 4908 4909 4910 4911 4912 4913 4914 4915 4916 4917 4918 4919 4920 4921 4922 4923 4924 4925 4926 4927 4928 4929 4930 4931 4932 4933 4934 4935 4936 4937 4938 4939 4940 4941 4942 4943 4944 4945 4946 4947 4948 4949 4950 4951 4952 4953 4954 4955 4956 4957 4958 4959 4960 4961 4962 4963 4964 4965 4966 4967 4968 4969 4970 4971 4972 4973 4974 4975 4976 4977 4978 4979 4980 4981 4982 4983 4984 4985 4986 4987 4988 4989 4990 4991 4992 4993 4994 4995 4996 4997 4998 4999 5000 5001 5002 5003 5004 5005 5006 5007 5008 5009 5010 5011 5012 5013 5014 5015 5016 5017 5018 5019 5020 5021 5022 5023 5024 5025 5026 5027 5028 5029 5030 5031 5032 5033 5034 5035 5036 5037 5038 5039 5040 5041 5042 5043 5044 5045 5046 5047 5048 5049 5050 5051 5052 5053 5054 5055 5056 5057 5058 5059 5060 5061 5062 5063 5064 5065 5066 5067 5068 5069 5070 5071 5072 5073 5074 5075 5076 5077 5078 5079 5080 5081 5082 5083 5084 5085 5086 5087 5088 5089 5090 5091 5092 5093 5094 5095 5096 5097 5098 5099 5100 5101 5102 5103 5104 5105 5106 5107 5108 5109 5110 5111 5112 5113 5114 5115 5116 5117 5118 5119 5120 5121 5122 5123 5124 5125 5126 5127 5128 5129 5130 5131 5132 5133 5134 5135 5136 5137 5138 5139 5140 5141 5142 5143 5144 5145 5146 5147 5148 5149 5150 5151 5152 5153 5154 5155 5156 5157 5158 5159 5160 5161 5162 5163 5164 5165 5166 5167 5168 5169 5170 5171 5172 5173 5174 5175 5176 5177 5178 5179 5180 5181 5182 5183 5184 5185 5186 5187 5188 5189 5190 5191 5192 5193 5194 5195 5196 5197 5198 5199 5200 5201 5202 5203 5204 5205 5206 5207 5208 5209 5210 5211 5212 5213 5214 5215 5216 5217 5218 5219 5220 5221 5222 5223 5224 5225 5226 5227 5228 5229 5230 5231 5232 5233 5234 5235 5236 5237 5238 5239 5240 5241 5242 5243 5244 5245 5246 5247 5248 5249 5250 5251 5252 5253 5254 5255 5256 5257 5258 5259 5260 5261 5262 5263 5264 5265 5266 5267 5268 5269 5270 5271 5272 5273 5274 5275 5276 5277 5278 5279 5280 5281 5282 5283 5284 5285 5286 5287 5288 5289 5290 5291 5292 5293 5294 5295 5296 5297 5298 5299 5300 5301 5302 5303 5304 5305 5306 5307 5308 5309 5310 5311 5312 5313 5314 5315 5316 5317 5318 5319 5320 5321 5322 5323 5324 5325 5326 5327 5328 5329 5330 5331 5332 5333 5334 5335 5336 5337 5338 5339 5340 5341 5342 5343 5344 5345 5346 5347 5348 5349 5350 5351 5352 5353 5354 5355 5356 5357 5358 5359 5360 5361 5362 5363 5364 5365 5366 5367 5368 5369 5370 5371 5372 5373 5374 5375 5376 5377 5378 5379 5380 5381 5382 5383 5384 5385 5386 5387 5388 5389 5390 5391 5392 5393 5394 5395 5396 5397 5398 5399 5400 5401 5402 5403 5404 5405 5406 5407 5408 5409 5410 5411 5412 5413 5414 5415 5416 5417 5418 5419 5420 5421 5422 5423 5424 5425 5426 5427 5428 5429 5430 5431 5432 5433 5434 5435 5436 5437 5438 5439 5440 5441 5442 5443 5444 5445 5446 5447 5448 5449 5450 5451 5452 5453 5454 5455 5456 5457 5458 5459 5460 5461 5462 5463 5464 5465 5466 5467 5468 5469 5470 5471 5472 5473 5474 5475 5476 5477 5478 5479 5480 5481 5482 5483 5484 5485 5486 5487 5488 5489 5490 5491 5492 5493 5494 5495 5496 5497 5498 5499 5500 5501 5502 5503 5504 5505 5506 5507 5508 5509 5510 5511 5512 5513 5514 5515 5516 5517 5518 5519 5520 5521 5522 5523 5524 5525 5526 5527 5528 5529 5530 5531 5532 5533 5534 5535 5536 5537 5538 5539 5540 5541 5542 5543 5544 5545 5546 5547 5548 5549 5550 5551 5552 5553 5554 5555 5556 5557 5558 5559 5560 5561 5562 5563 5564 5565 5566 5567 5568 5569 5570 5571 5572 5573 5574 5575 5576 5577 5578 5579 5580 5581 5582 5583 5584 5585 5586 5587 5588 5589 5590 5591 5592 5593 5594 5595 5596 5597 5598 5599 5600 5601 5602 5603 5604 5605 5606 5607 5608 5609 5610 5611 5612 5613 5614 5615 5616 5617 5618 5619 5620 5621 5622 5623 5624 5625 5626 5627 5628 5629 5630 5631 5632 5633 5634 5635 5636 5637let validate_refs = ref true module Uint32 = Wax_utils.Uint32 module Uint64 = Wax_utils.Uint64 open Types.Internal module Nz = Types.Normalized (* The [@]-suffixed operators sequence [option] computations, short-circuiting on [None]: [let*@] binds, [let+@] maps, and [let>@] runs the body for its side effect and discards the result. (The unsuffixed [let*]/[let*!]/[let*?] defined further down instead thread the value stack.) *) let ( let*@ ) = Option.bind let ( let+@ ) o f = Option.map f o let ( let>@ ) o f = Option.iter f o (*** Source types and printers ***) (* WAT types and identifiers are rendered directly into a diagnostic's styled printer (see {!Wax_utils.Styled_printer}), so an embedded type shares the message's colour theme and width — rather than being pre-rendered to a flat string. These helpers wrap the layout/colour primitives. *) let sp_space pp = Wax_utils.Printer.space pp.Wax_utils.Styled_printer.printer () let sp_box pp f = Wax_utils.Printer.box pp.Wax_utils.Styled_printer.printer ~indent:1 f let sp_type pp s = Wax_utils.Styled_printer.print_styled pp Wax_utils.Colors.Type s let sp_kw pp s = Wax_utils.Styled_printer.print_styled pp Wax_utils.Colors.Keyword s let sp_punct pp s = Wax_utils.Styled_printer.print_styled pp Wax_utils.Colors.Punctuation s let print_string pp s = let len, escaped = Wax_utils.Unicode.escape_string s.Ast.desc in Wax_utils.Styled_printer.print_styled pp Wax_utils.Colors.String ~len:(Some len) escaped let print_ident pp id = let s = if Lexer.is_valid_identifier id then "$" ^ id else "$\"" ^ snd (Wax_utils.Unicode.escape_string id) ^ "\"" in Wax_utils.Styled_printer.print_styled pp Wax_utils.Colors.Identifier s let print_index pp (idx : Ast.Text.idx) = match idx.desc with | Num n -> Wax_utils.Styled_printer.print_styled pp Wax_utils.Colors.Constant (Uint32.to_string n) | Id id -> print_ident pp id (* Render a type as the source wrote it, naming an indexed type by its source reference ($foo or a number) rather than an interned canonical index. *) let print_text_heaptype pp (ty : Ast.Text.heaptype) = match Ast.Text.heaptype_keyword ty with | Some kw -> sp_type pp kw | None -> ( match ty with | Type idx -> print_index pp idx | Exact idx -> sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "exact"; sp_space pp; print_index pp idx; sp_punct pp ")") | _ -> assert false) let print_text_valtype pp (ty : Ast.Text.valtype) = match ty with | I32 -> sp_type pp "i32" | I64 -> sp_type pp "i64" | F32 -> sp_type pp "f32" | F64 -> sp_type pp "f64" | V128 -> sp_type pp "v128" | Ref { nullable; typ } -> sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "ref"; sp_space pp; if nullable then ( sp_kw pp "null"; sp_space pp); print_text_heaptype pp typ; sp_punct pp ")") let print_text_storagetype pp (ty : Ast.Text.storagetype) = match ty with | Value v -> print_text_valtype pp v | Packed I8 -> sp_type pp "i8" | Packed I16 -> sp_type pp "i16" let print_text_fieldtype pp ({ mut; typ } : Ast.Text.fieldtype) = if mut then sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "mut"; sp_space pp; print_text_storagetype pp typ; sp_punct pp ")") else print_text_storagetype pp typ let print_text_functype pp ({ params; results } : Ast.Text.functype) = Array.iter (fun p -> sp_space pp; sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "param"; sp_space pp; print_text_valtype pp (snd p.Ast.desc); sp_punct pp ")")) params; Array.iter (fun t -> sp_space pp; sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "result"; sp_space pp; print_text_valtype pp t; sp_punct pp ")")) results (* Render a composite type as its source signature, for a reference to a type the user did not name (an implicit [ref.func] type, the internal string type). *) let print_text_comptype pp (ty : Ast.Text.comptype) = match ty with | Func ft -> sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "func"; print_text_functype pp ft; sp_punct pp ")") | Array ft -> sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "array"; sp_space pp; print_text_fieldtype pp ft; sp_punct pp ")") | Struct fields -> sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "struct"; Array.iter (fun e -> let _, ft = e.Ast.desc in sp_space pp; sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "field"; sp_space pp; print_text_fieldtype pp ft; sp_punct pp ")")) fields; sp_punct pp ")") | Cont idx -> sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "cont"; sp_space pp; print_index pp idx; sp_punct pp ")") (* The source rendering of a stack value: either a value type the user wrote (or that names a type the user declared), or — for a reference whose type has no source name — that referenced type's signature, shown inline. *) type source_type = | Plain of Ast.Text.valtype | Inline_ref of Ast.Text.comptype (* The bottom reference type [(ref bot)]. It has no user-written form; it is synthesized only to render a stack value of the bottom reference type in a diagnostic (e.g. when such a value reaches a numeric context). *) | Bottom_ref let print_source_type pp = function | Plain v -> print_text_valtype pp v | Inline_ref comptype -> sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "ref"; sp_space pp; print_text_comptype pp comptype; sp_punct pp ")") | Bottom_ref -> sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "ref"; sp_space pp; sp_type pp "bot"; sp_punct pp ")") (* Render a source type to a plain (uncoloured) string. *) let render_source_type source = let buf = Buffer.create 32 in let fmt = Format.formatter_of_buffer buf in Wax_utils.Printer.run fmt (fun p -> let pp = Wax_utils.Styled_printer.create ~printer:p ~theme:Wax_utils.Colors.no_color ~trivia:(Hashtbl.create 0) () in print_source_type pp source); Format.pp_print_flush fmt (); String.trim (Buffer.contents buf) (* What the editor type sink records at each instruction span. Kept unrendered: the recording pass runs over the whole module, but the editor renders only the few entries under the cursor, so rendering is deferred to [render_recorded_type]. *) type recorded_type = | Pushed of source_type (* a value the instruction leaves on the stack *) | Polymorphic (* the unknown value of an unreachable / polymorphic stack *) | No_result (* the instruction produces no value *) | Signature of source_type array * source_type array (* a function's (params, results), for the identifier of a call / ref.func *) | Subtype of (Ast.Text.name option * Ast.Text.subtype, Ast.location) Ast.annotated (* the source definition of the type a type identifier refers to *) | Value_type of Ast.location (* the definition span of a value's named reference type, for go-to-type-def; carries no display type (a [Pushed] at the same span renders the value) *) let render_recorded_type = function | Pushed source -> Some (render_source_type source) | Polymorphic -> Some "any" | No_result | Value_type _ -> None | Subtype e -> Some (Output.subtype_string e) | Signature (params, results) -> let group kw arr = if Array.length arr = 0 then None else Some (Printf.sprintf "(%s %s)" kw (String.concat " " (Array.to_list (Array.map render_source_type arr)))) in let parts = List.filter_map Fun.id [ group "param" params; group "result" results ] in Some (Printf.sprintf "(func%s)" (String.concat "" (List.map (fun s -> " " ^ s) parts))) (* The definition span of the type a recorded entry refers to, for go-to-type-definition: a value's named reference type, or the type a type identifier names; [None] otherwise. *) let type_def_location = function | Value_type l -> Some l | Subtype e -> ( match fst e.Ast.desc with | Some (n : Ast.Text.name) -> Some n.Ast.info | None -> Some e.Ast.info) | Pushed _ | Polymorphic | No_result | Signature _ -> None (* The rendered parameter and result types of a recorded function signature (a [call]/[ref.func] identifier), for signature help; [None] for any other kind. *) let signature_labels = function | Signature (params, results) -> Some ( Array.to_list (Array.map render_source_type params), Array.to_list (Array.map render_source_type results) ) | _ -> None (* Editor type sink. Like [validate_refs], a module-level ref rather than a threaded parameter: the push chokepoints below record into it without carrying it through the ~130 call sites. When set (only in editor mode, via [f]'s [?record_types]), every value pushed onto the stack is recorded as [(span of the pushing instruction, configuration index, type)] — the raw material for WAT hover. The configuration index distinguishes entries from different explored configurations (conditional compilation), so a consumer can join a single configuration's stack results as a tuple yet keep the types a config-varying span takes across configurations apart. [None] on ordinary validation, so the recording is free. *) let recorded_types : (Ast.location * int * recorded_type) list ref option ref = ref None (* The configuration currently being validated (0 for a module without conditional compilation; bumped for each configuration {!Cond_explore} explores), tagging every recorded entry. *) let sink_config = ref 0 (* Record [rt] at instruction span [loc], if the sink is active and [loc] is a real source span (not a synthesized / recovery placeholder). No rendering here — an ordinary validation pays nothing beyond the [!recorded_types] test. *) let record loc rt = match (!recorded_types, loc) with | Some r, Some l when l.Ast.loc_start.Lexing.pos_cnum >= 0 -> r := (l, !sink_config, rt) :: !r | _ -> () (* A named index with a zero-width span is one error recovery synthesized in place — the placeholder [$_] it inserts for a missing index ([(call)] repaired to [(call $_)]). A diagnostic anchored solely to it (the placeholder name being unbound) is suppressed: the "Missing index" syntax error already stands there. The check is narrow on purpose — a real [$id] spans at least two characters, and an {e omitted} index that defaults to [0] (e.g. the implicit memory of [memory.copy]) is a [Num], whose unbound-ness is a genuine error — so only the synthetic named placeholder is caught. *) let is_recovery_placeholder (idx : Ast.Text.idx) = match idx.Ast.desc with | Ast.Text.Id _ -> idx.info.Ast.loc_start.Lexing.pos_cnum = idx.info.Ast.loc_end.Lexing.pos_cnum | Ast.Text.Num _ -> false (* Reconstruct a source type from an interned one. Used as the source type of a pushed value when no truer reference is available, so every concrete stack value carries a source type (as on the Wax side, where an inferred type bundles both forms). Only abstract heap types are reconstructed this way: a concrete [Type] reference always carries a truer source from its declaration. *) let source_of_heaptype (h : heaptype) : Ast.Text.heaptype = match h with | Func -> Func | NoFunc -> NoFunc | Exn -> Exn | NoExn -> NoExn | Cont -> Cont | NoCont -> NoCont | Extern -> Extern | NoExtern -> NoExtern | Any -> Any | Eq -> Eq | I31 -> I31 | Struct -> Struct | Array -> Array | None_ -> None_ | Type _ | Exact _ -> assert false let source_of_valtype (ty : valtype) : source_type = Plain (match ty with | I32 -> I32 | I64 -> I64 | F32 -> F32 | F64 -> F64 | V128 -> V128 | Ref { nullable; typ } -> Ref { nullable; typ = source_of_heaptype typ }) (*** Diagnostics ***) module Error = struct open Wax_utils module D = Diagnostic (* Message-building combinators (see {!Wax_utils.Message}). Prose is [text], joined with [++] (soft, wrap-point space) or [^^] (no space). An emphasized atom — [styp]/[sources] a source type, [index]/[ident] an identifier, [str] a string literal, [num] a numeric literal — is coloured when the theme is coloured and quoted ['…'] when it is not. Its text is produced by the [Format] [print_*] printers above and emitted as one styled atom. *) let text = Message.text let ( ++ ) = Message.( ++ ) let ( ^^ ) = Message.( ^^ ) (* Render an AST fragment ([render], drawing into the styled printer) as one emphasized atom: coloured when the theme is coloured, wrapped in ['…'] when it is not. [style] is the atom's colour — forced over the whole fragment (via [with_style]) so a type reads as one unit rather than syntax- highlighting its parens/keywords/idents in separate role colours — and it also decides the quoting. *) let styled_atom style render = Message.raw (fun pp -> let p = pp.Styled_printer.printer in let quote = Colors.escape_sequence pp.Styled_printer.theme style = "" in if quote then Printer.string p "'"; Styled_printer.with_style pp style (fun () -> render pp); if quote then Printer.string p "'") let styp source = styled_atom Colors.Type (fun pp -> print_source_type pp source) let index idx = styled_atom Colors.Identifier (fun pp -> print_index pp idx) let ident id = styled_atom Colors.Identifier (fun pp -> print_ident pp id) let str s = styled_atom Colors.String (fun pp -> print_string pp s) let num s = Message.styled Colors.Constant s let report context ~location ~severity ?warning ?universal ?hint ? message = (* In error-recovery mode (see [Parsing.parse_recover], used by the editor to validate a best-effort partial AST across syntax errors) the module's whole-module analyses are unreliable, so suppress every warning — the same policy the Wax typer applies in recovery. Errors still surface, so a real defect in an intact region shows; the few error {e cascades} a dropped or auto-closed construct triggers are suppressed at their own call sites (see [empty_stack]/[non_empty_stack]/[leftover_values]). *) match severity with | D.Warning when D.in_recovery context -> () | _ -> D.report context ~location ~severity ?warning ?universal ?hint ?related ~message () let did_you_mean = function | [] -> None | suggestions -> Some (text "Did you mean" ++ Message.enumerate ~conj:"or" (List.map Message.ident suggestions) ^^ text "?") let unbound_label context ~location id lst = if is_recovery_placeholder id then () else report context ~location ~severity:Error ?hint:(did_you_mean lst) (text "Unknown label:" ++ index id ++ text "is not bound.") let unbound_index context ~location kind id lst = if is_recovery_placeholder id then () else report context ~location ~severity:Error ?hint:(did_you_mean lst) ((text "Unknown" ++ text kind) ^^ (text ": index" ++ index id ++ text "is not bound.")) let packed_array_access context ~location = report context ~location ~severity:Error (text "This instruction cannot be used on packed arrays. Use array.get_s or \ array.get_u to specify sign extension.") let unpacked_array_access context ~location = report context ~location ~severity:Error (text "This instruction is only valid for packed arrays. Use array.get.") let packed_struct_access context ~location = report context ~location ~severity:Error (text "This instruction cannot be used on packed fields. Use struct.get_s \ or struct.get_u to specify sign extension.") let unpacked_struct_access context ~location = report context ~location ~severity:Error (text "This instruction is only valid for packed fields. Use struct.get.") (* The caret points at the instruction that {e produced} the value still on the stack, not at the one consuming it (whose location is [consumer]): the wording makes that explicit, and a secondary caret marks the use site. *) let instruction_type_mismatch context ~location ~consumer ~provided_source ~expected_source = (* Mark the use site with a secondary caret, but only when it is a distinct location that does not enclose the producer: an implicit function or block result spans the whole construct, so a caret there would just be noise around the precise one. *) let encloses outer inner = outer.Ast.loc_start.Lexing.pos_cnum <= inner.Ast.loc_start.Lexing.pos_cnum && inner.Ast.loc_end.Lexing.pos_cnum <= outer.Ast.loc_end.Lexing.pos_cnum in let = match consumer with | Some loc when loc.Ast.loc_start.Lexing.pos_cnum >= 0 && not (encloses loc location) -> [ { Wax_utils.Diagnostic.location = loc; message = text "expected here"; }; ] | _ -> [] in report context ~location ~severity:Error ~related (text "Type mismatch: this produces a value of type" ++ styp provided_source ^^ text "," ++ text "but type" ++ styp expected_source ++ text "is expected.") let expected_ref_type context ~location ~src_loc ~source = match src_loc with | None -> report context ~location ~severity:Error (text "Type mismatch: expected reference type but got type" ++ styp source ^^ text ".") | Some location -> report context ~location ~severity:Error (text "Type mismatch: this instruction should return a reference type \ but has type" ++ styp source ^^ text ".") let table_type_mismatch context ~location ~source idx = report context ~location ~severity:Error (text "Type mismatch: the table" ++ index idx ++ text "should contain functions but its elements have type" ++ styp source ^^ text ".") let elem_segment_type_mismatch context ~location ~elem_source ~table_source = report context ~location ~severity:Error ((text "Type mismatch: the element segment has type" ++ styp elem_source) ^^ text "," ++ text "which is not a subtype of the table element type" ++ styp table_source ^^ text ".") let duplicate_local context ~location name = report context ~location ~severity:Error (text "The local" ++ ident name ++ text "is already defined.") let type_mismatch context ~location ~provided_source ~expected_source = report context ~location ~severity:Error (text "Type mismatch: expecting type" ++ styp expected_source ++ text "but got type" ++ styp provided_source ^^ text ".") let br_cast_type_mismatch context ~location = report context ~location ~severity:Error (text "Type mismatch: the first type must be a supertype of the second one.") let br_on_non_null_no_ref context ~location = report context ~location ~severity:Error (text "Type mismatch: br_on_non_null requires the target label to end in a \ reference type, but it has no result types.") let select_type_mismatch context ~location ~loc1 ~source1 ~loc2 ~source2 = (* Point a caret at each branch value (when its push site is known), labelled with its type. A placeholder location uses a negative column; skip those, as in [locations]. *) let branch_label loc source = match loc with | Some loc when loc.Ast.loc_start.Lexing.pos_cnum >= 0 -> Some { Wax_utils.Diagnostic.location = loc; message = styp source } | _ -> None in let = List.filter_map Fun.id [ branch_label loc1 source1; branch_label loc2 source2 ] in (* When both carets are shown they carry the types; otherwise name the two types in the message so they are not lost. *) let message = if List.length related = 2 then text "Type mismatch: both branches of a select should have the same type." else text "Type mismatch: both branches of a select should have the same type." ++ text "Here, they have type" ++ styp source1 ++ text "and" ++ styp source2 ^^ text "." in report context ~location ~severity:Error ~related message (* The stack-shape mismatches ([empty_stack], [non_empty_stack], [leftover_values]) are the error cascades a partial AST triggers: an auto-closed body ([(func (i32.const 1)] at EOF) or a dropped instruction leaves the operand stack the wrong height through no fault of the intact code. Suppress them in recovery mode — the analogue of the Wax typer dropping leftover Error-typed values under [with_empty_stack]. *) let empty_stack context ~location = if D.in_recovery context then () else report context ~location ~severity:Error (text "Type mismatch: the stack is empty (a value is missing).") let non_empty_stack context ~location render = if D.in_recovery context then () else report context ~location ~severity:Error (text "Type mismatch: unexpected values left on the stack:" ^^ Message.raw render) (* Report the values still on the stack by pointing a caret at each of them. [location] carries the topmost value; [related] the others. *) let leftover_values context ~location ~ = if D.in_recovery context then () else report context ~location ~severity:Error ~related (text (if related = [] then "Type mismatch: this value is left on the stack." else "Type mismatch: these values are left on the stack.")) (* Print a list of source types, [\[a b c\]]. *) let print_sources pp source = sp_box pp (fun () -> sp_punct pp "["; Array.iteri (fun i s -> if i > 0 then sp_space pp; print_source_type pp s) source; sp_punct pp "]") let sources source = styled_atom Colors.Type (fun pp -> print_sources pp source) let argument_count_mismatch context ~location ~descr ~provided_source ~expected_source = report context ~location ~severity:Error (text "Type mismatch:" ++ text descr ++ text "provides type" ++ sources provided_source ++ text "but type" ++ sources expected_source ++ text "was expected.") let argument_type_mismatch context ~location ~descr ~provided_source ~expected_source = report context ~location ~severity:Error (text "Type mismatch:" ++ text descr ++ text "provides type" ++ styp provided_source ++ text "but type" ++ styp expected_source ++ text "was expected.") let branch_parameter_count_mismatch context ~location label len label' len' = report context ~location ~severity:Error (text "Type mismatch: the default branch target" ++ index label ++ text "expects" ++ Message.int len ++ text "parameters, while branch target" ++ index label' ++ text "expects" ++ Message.int len' ++ text "parameters.") let memory_offset_too_large context ~location max_offset = report context ~location ~severity:Error (text "The memory offset should be less than" ++ num (Printf.sprintf "0x%Lx" (Uint64.to_int64 max_offset)) ^^ text ".") let memory_align_too_large context ~location natural = report context ~location ~severity:Error (text "The memory alignment is larger than the natural alignment" ++ Message.int natural ^^ text ".") let bad_memory_align context ~location = report context ~location ~severity:Error (text "The memory alignment should be a power of two.") let atomic_alignment context ~location natural = report context ~location ~severity:Error (text "The alignment of an atomic access must be its natural alignment" ++ Message.int natural ^^ text ".") let invalid_lane_index context ~location max_lane = report context ~location ~severity:Error ((text "The lane index should be less than" ++ Message.int max_lane) ^^ text ".") let inline_function_type_mismatch context ~location _ = (*ZZZ print expected type *) report context ~location ~severity:Error (text "The inline function type does not match the type definition.") let constant_expression_required context ~location = report context ~location ~severity:Error (text "Only constant expressions are allowed here.") let immutable_global context ~location idx = report context ~location ~severity:Error (text "The global" ++ index idx ++ text "should be mutable.") let limit_too_large context ~location kind max = report context ~location ~severity:Error (text "The" ++ text kind ++ text "size is too large. It should be less than" ++ num (Printf.sprintf "0x%Lx" (Uint64.to_int64 max)) ^^ text ".") let invalid_page_size context ~location = report context ~location ~severity:Error (text "The custom page size must be 1 or 65536.") let branch_hint_invalid_target context ~location = report context ~location ~severity:Error (text "A branch hint may only prefix a conditional branch (if, br_if, or \ br_on_*).") let context ~location = report context ~location ~severity:Error (text "A shared memory must have a maximum size.") let limit_mismatch context ~location kind = report context ~location ~severity:Error (text "The" ++ text kind ++ text "maximum size should be larger than the minimal size.") let duplicated_export context ~location name = report context ~location ~severity:Error ((text "There is already an export of name" ++ str name) ^^ text ".") let import_after_definition context ~location kind = report context ~location ~severity:Error (text "This import is after a" ++ text kind ++ text "definition.") let supertype_mismatch context ~location = report context ~location ~severity:Error (text "The supertype is not of the same kind as this type.") let invalid_subtype context ~location = report context ~location ~severity:Error (text "This type is not a valid subtype of its declared supertype.") let descriptor_outside_rec_group context ~location ~described = report context ~location ~severity:Error (text "The" ++ text (if described then "described" else "descriptor") ++ text "type must be in the same recursion group.") let descriptor_not_reciprocal context ~location ~described = report context ~location ~severity:Error (text (if described then "This descriptor does not describe the type it is attached to." else "The descriptor of this type does not describe it back.")) let forward_use_of_described context ~location = report context ~location ~severity:Error (text "A described type must be declared before its descriptor.") let descriptor_not_struct context ~location ~described = report context ~location ~severity:Error (text "A" ++ text (if described then "described" else "descriptor") ++ text "type must be a struct type.") let not_function_type context ~location = report context ~location ~severity:Error (text "This should be a function type.") let exception_tag_with_results context ~location = report context ~location ~severity:Error (text "The type of an exception tag must have no results.") let select_result_count context ~location = report context ~location ~severity:Error (text "A typed select must be annotated with exactly one result type.") let non_nullable_table_type context ~location = report context ~location ~severity:Error (text "Type mismatch: the type of the elements of this table must be \ nullable.") let uninitialized_local context ~location idx = report context ~location ~severity:Error (text "The local variable" ++ index idx ++ text "has not been initialized.") (* A local that is declared but never read. Prefix its name with [_] to silence the warning. *) let unused_local context ~location name = report context ~location ~severity:Warning ~warning:Warning.Unused_local ~universal:true (match name with | Some id -> text "The local variable" ++ ident id ++ text "is never used." | None -> text "This local is never used.") (* A module field (a function or global) defined but never referenced, exported, or used as the start function. Prefix its name with [_] to silence the warning. *) let unused_field context ~location kind name = report context ~location ~severity:Warning ~warning:Warning.Unused_field ~universal:true (match name with | Some id -> text "The" ++ text kind ++ ident id ++ text "is never used." | None -> text "This" ++ text kind ++ text "is never used.") (* An imported function or global never referenced, exported, or used as the start function. Prefix its name with [_] to silence the warning. *) let unused_import context ~location kind name = report context ~location ~severity:Warning ~warning:Warning.Unused_import ~universal:true (match name with | Some id -> text "The imported" ++ text kind ++ ident id ++ text "is never used." | None -> text "This imported" ++ text kind ++ text "is never used.") (* A block label declared but never branched to. Prefix its name with [_] to silence the warning. *) let unused_label context ~location name = report context ~location ~severity:Warning ~warning:Warning.Unused_label ~universal:true (text "The label" ++ ident name ++ text "is never used.") (* --- The correctness lint tier (shared with the Wax typer; same warnings and wording). Emitted while validating a WAT/WASM function body. --- *) let warn_lint context ~location ?hint ? warning message = report context ~location ~severity:Warning ~warning ~universal:true ?hint ?related message let shift_overflow context ~location ~width count = warn_lint context ~location Warning.Shift_overflow ~hint: ((text "Wasm masks the count modulo" ++ Message.int width) ^^ text "," ++ text "shifting by" ++ Message.int64 (Int64.rem count (Int64.of_int width)) ++ text "instead.") (text "The shift count" ++ Message.int64 count ++ text "is at least the operand width (" ^^ Message.int width ^^ text " bits).") let division_by_zero context ~location = warn_lint context ~location Warning.Constant_trap (text "This integer division or remainder by zero always traps.") let conversion_out_of_range context ~location = warn_lint context ~location Warning.Constant_trap (text "This conversion always traps: the constant is out of the target \ type's range.") let tautological_comparison context ~location ~value = warn_lint context ~location Warning.Tautological_comparison ((text "This comparison is always" ++ Message.bool value) ^^ text ".") let constant_condition context ~location ~value = warn_lint context ~location Warning.Constant_condition ((text "This condition is always" ++ Message.bool value) ^^ text ".") let unused_result context ~location = warn_lint context ~location Warning.Unused_result (text "The result of this expression is discarded, and computing it has no \ effect.") let dead_code context ~location ~ = warn_lint context ~location ~related Warning.Dead_code (text "This code is unreachable.") let redundant_operation context ~location message = warn_lint context ~location Warning.Redundant_operation message let cast_always_fails context ~location ~is_test = warn_lint context ~location Warning.Cast_always_fails (text (if is_test then "This type test is always false: the value can never have this \ type." else "This cast always traps: the value can never have this type.")) let redundant_cast context ~location ~is_test = warn_lint context ~location Warning.Redundant_operation (text (if is_test then "This type test is always true: the value already has this type." else "This cast is redundant: the value already has this type.")) (* A trapping or effectful operation among the value operands of a [select], which evaluates both operands unconditionally. Mirrors the Wax typer's [eager-select] lint (a Wax [?:] compiles to a [select]). [select] points at the [select] instruction. *) let eager_select context ~location ~select = warn_lint context ~location Warning.Eager_select ~related: [ { Wax_utils.Diagnostic.location = select; message = text "This 'select' evaluates both of its operands."; }; ] (text "This operation is evaluated even when the condition selects the \ other operand.") let index_already_bound context ~location kind index = report context ~location ~severity:Error (text "The" ++ text kind ++ text "index" ++ ident index.Ast.desc ++ text "is already bound.") let expected_func_type context ~location idx = report context ~location ~severity:Error (text "Type" ++ index idx ++ text "should be a function type.") let expected_struct_type context ~location idx = report context ~location ~severity:Error (text "Type" ++ index idx ++ text "should be a struct type.") let expected_array_type context ~location idx = report context ~location ~severity:Error (text "Type" ++ index idx ++ text "should be an array type.") let expected_cont_type context ~location idx = report context ~location ~severity:Error (text "Type" ++ index idx ++ text "should be a continuation type.") let stack_switching_type_mismatch context ~location ~descr = report context ~location ~severity:Error ((text "Type mismatch in this stack switching instruction:" ++ text descr) ^^ text ".") let invalid_cast_type context ~location = report context ~location ~severity:Error (text "Continuation types cannot be used in a cast instruction.") let type_without_descriptor context ~location = report context ~location ~severity:Error (text "This descriptor instruction requires a type that has a descriptor.") let feature_disabled context ~location feature = report context ~location ~severity:Error (text "This uses the" ++ text (Wax_utils.Feature.name feature) ++ text "feature, which is not enabled; pass --feature" ++ text (Wax_utils.Feature.name feature) ^^ text ".") let unknown_feature context ~location name = report context ~location ~severity:Error ((text "Unknown feature" ++ Message.code name) ^^ text ". Known features:" ++ text (String.concat ", " (List.map Wax_utils.Feature.name Wax_utils.Feature.all)) ^^ text ".") let feature_conflict context ~location feature = report context ~location ~severity:Error (text "This module requires the" ++ text (Wax_utils.Feature.name feature) ++ text "feature, which is disabled on the command line; drop --feature" ++ text (Wax_utils.Feature.name feature ^ "=off") ^^ text ".") let descriptor_allocation_required context ~location = report context ~location ~severity:Error (text "A type with a descriptor must be allocated with a descriptor \ (struct.new_desc / struct.new_default_desc).") let expected_number_or_vec context ~location ~source = report context ~location ~severity:Error (text "Type mismatch: expecting a numeric or vector type but got type" ++ styp source ^^ text ".") let immutable context ~location what = report context ~location ~severity:Error (text "This" ++ text what ++ text "is immutable.") let not_defaultable context ~location = report context ~location ~severity:Error (text "This type has no default value for all its fields.") let field_index_out_of_bounds context ~location ~index ~count = report context ~location ~severity:Error (text "The field index" ++ Message.int index ++ text "is out of bounds: the structure has" ++ Message.int count ++ text "field(s).") let unknown_field context ~location = report context ~location ~severity:Error (text "There is no such field.") let numeric_array_required context ~location = report context ~location ~severity:Error (text "This operation requires an array of numeric elements.") let string_array_required context ~location = report context ~location ~severity:Error (text "A string can only build an i8 or i16 array.") let string_not_unicode context ~location = report context ~location ~severity:Error (text "A string building an i16 array must be a valid Unicode string.") let incompatible_array_element context ~location = report context ~location ~severity:Error (text "The array element type is incompatible.") let ref_func_inaccessible context ~location idx = report context ~location ~severity:Error (text "The function" ++ index idx ++ text "is not declared as referenceable (ref.func).") let non_constant_global context ~location idx = report context ~location ~severity:Error (text "Only an immutable global may be used in a constant expression:" ++ index idx ^^ text ".") let start_function_signature context ~location = report context ~location ~severity:Error (text "The start function must have no parameters and no results.") let multiple_start context ~location = report context ~location ~severity:Error (text "A module can have at most one start function.") end let print_instr f i = Wax_utils.Printer.run f (fun p -> Output.instr p i) (*** Symbol tables (sequences) ***) module Sequence = struct type 'a t = { name : string; index_mapping : (int, 'a) Hashtbl.t; label_mapping : (string, int) Hashtbl.t; mutable last_index : int; } let make name = { name; index_mapping = Hashtbl.create 16; label_mapping = Hashtbl.create 16; last_index = 0; } (* The index the next [register] will assign (the current length). *) let next_index seq = seq.last_index let register seq id v = let idx = seq.last_index in seq.last_index <- seq.last_index + 1; Hashtbl.add seq.index_mapping idx v; Option.iter (fun id -> Hashtbl.add seq.label_mapping id.Ast.desc idx) id let get d seq (idx : Ast.Text.idx) = try match idx.desc with | Num n -> Some (Hashtbl.find seq.index_mapping (Uint32.to_int n)) | Id id -> Some (Hashtbl.find seq.index_mapping (Hashtbl.find seq.label_mapping id)) with Not_found -> let lst = match idx.desc with | Num _ -> [] | Id id -> Wax_utils.Spell_check.f (fun f -> Hashtbl.iter (fun id' _ -> f id') seq.label_mapping) id in Error.unbound_index d ~location:idx.info seq.name idx lst; None let get_index seq (idx : Ast.Text.idx) = match idx.desc with | Num n -> Uint32.to_int n | Id id -> ( try Hashtbl.find seq.label_mapping id with Not_found -> assert false (* Should not happen *)) (* Resolve to an index without reporting or raising when a name is unbound — for callers that only want to note a resolvable reference and leave the error reporting to the pass that validates the reference. *) let get_index_opt seq (idx : Ast.Text.idx) = match idx.desc with | Num n -> Some (Uint32.to_int n) | Id id -> Hashtbl.find_opt seq.label_mapping id end (*** Types and the type context ***) type type_context = { types : Types.t; mutable last_index : int; (* Keyed by a source type reference (numeric index / name): the resolved global index, a struct's field-name-to-position map, and the source composite type. The last lets an error name a component (a struct field, an array element, a function result) as the source wrote it; keying by the reference (rather than the deduplicated global index) keeps it injective, so [$a] is named with [$a] even when a structurally-equal [$b] shares its global index. *) (* The fourth component is the source subtype definition — kept, keyed by the (injective) source reference so hover on a type identifier shows the type as written; [None] for a synthesized (implicit) function type, which has no source. *) index_mapping : ( Uint32.t, Types.ref_index * (string * int) list * Ast.Text.comptype * (Ast.Text.name option * Ast.Text.subtype, Ast.location) Ast.annotated option ) Hashtbl.t; label_mapping : ( string, Types.ref_index * (string * int) list * Ast.Text.comptype * (Ast.Text.name option * Ast.Text.subtype, Ast.location) Ast.annotated option ) Hashtbl.t; (* For each type definition, keyed by its text-level index: its source index node (its name when it has one, else its numeric index, carrying the definition's location), and — for a continuation type — the source reference to the wrapped type. Keying by text index (rather than the resolved, deduplicated global index) keeps the mapping injective, so a check on a type is reported at the exact definition and names types as they appear in the source. *) type_defs : (int, Ast.Text.idx * Ast.Text.idx option) Hashtbl.t; (* For a type carrying a [descriptor] clause, keyed by its resolved global index: the source reference to its descriptor type. The descriptor instructions derive the descriptor type from the described one, so this recovers the descriptor's source name for error rendering (there is no immediate to name it by). Deduplicated types share a descriptor, so keying by the global index is unambiguous. *) descriptor_source : (Types.Id.t, Ast.Text.idx) Hashtbl.t; (* The enabled optional features / proposals, and which are used. *) features : Wax_utils.Feature.set; } (* The source composite type a reference resolves to, named as the source wrote it (injective), or [None] for an unbound or sourceless reference. Does not report errors — callers that resolve the reference do. *) let reference_comptype tc (idx : Ast.Text.idx) = let _, _, c, _ = match idx.desc with | Num x -> Hashtbl.find tc.index_mapping x | Id id -> Hashtbl.find tc.label_mapping id in c (* The source function type a reference resolves to, when it names one. *) let reference_functype tc idx = match reference_comptype tc idx with | Func ft -> ft | Struct _ | Array _ | Cont _ -> assert false (* The source function type a [typeuse] denotes: the one named by its type reference, or its inline signature. Resolve the reference in preference to the inline signature, consistent with [typeuse] (which drives the corresponding [return_types]); for valid input the two agree, and preferring one uniformly keeps their arities in step when a malformed module gives both a [(type $i)] and a disagreeing inline signature. *) let typeuse_functype tc (tu_idx, tu_sign) = match (tu_idx, tu_sign) with | Some idx, _ -> reference_functype tc idx | None, Some ft -> ft | _ -> assert false (* Per-element source types for a source function type's params and results, to pass straight to [pop_args]/[push_results]'s [~source]. *) let functype_sources ({ params; results } : Ast.Text.functype) = ( Array.map (fun p -> Plain (snd p.Ast.desc)) params, Array.map (fun v -> Plain v) results ) (* The source function type that the continuation type named by [idx] wraps. *) let cont_source_functype tc idx = match reference_comptype tc idx with | Cont r -> reference_functype tc r | _ -> assert false let get_type_info d ctx (idx : Ast.Text.idx) = let result = try match idx.desc with | Num x -> Some (Hashtbl.find ctx.index_mapping x) | Id id -> Some (Hashtbl.find ctx.label_mapping id) with Not_found -> let lst = match idx.desc with | Num _ -> [] | Id id -> Wax_utils.Spell_check.f (fun f -> Hashtbl.iter (fun id' _ -> f id') ctx.label_mapping) id in Error.unbound_index d ~location:idx.info "type" idx lst; None in (* Record the referenced type's source definition, so hover over the type identifier shows its subtype. Guarded on the sink, so an ordinary validation pays nothing. *) (match (!recorded_types, result) with | Some _, Some (_, _, _, Some e) -> record (Some idx.info) (Subtype e) | _ -> ()); result (* The type context in force during the current body validation, so [push] can resolve a pushed value's named reference type to the type's definition for go-to-type-definition. Set (editor mode only) by [validate_configuration]; like [recorded_types] it avoids threading through the push chokepoints. *) let sink_type_context : type_context option ref = ref None (* The source subtype entry a type reference resolves to (its definition), without reporting or recording — a silent [get_type_info]. *) let lookup_subtype_entry tc (idx : Ast.Text.idx) = match match idx.desc with | Num x -> Hashtbl.find_opt tc.index_mapping x | Id id -> Hashtbl.find_opt tc.label_mapping id with | Some (_, _, _, e) -> e | None -> None (* If [source] is a value of a named reference type, record its type's definition span at [loc] for go-to-type-definition. *) let record_value_type_def loc source = match (!recorded_types, loc, !sink_type_context) with | Some r, Some l, Some tc when l.Ast.loc_start.Lexing.pos_cnum >= 0 -> ( match source with | Plain (Ast.Text.Ref { typ = Type idx | Exact idx; _ }) -> ( match lookup_subtype_entry tc idx with | Some e -> let def = match fst e.Ast.desc with | Some (n : Ast.Text.name) -> n.Ast.info | None -> e.Ast.info in r := (l, !sink_config, Value_type def) :: !r | None -> ()) | _ -> ()) | _ -> () (* Resolve a source type reference to how it should appear inside a rec group being registered: [Def id] for an already-defined type, [Rec pos] for a member of the group currently under construction. This is what the type-definition builders below produce. *) let resolve_type_ref d ctx idx = let+@ r, _, _, _ = get_type_info d ctx idx in r (* The canonical index of an already-defined type. A [Rec] would mean referring to a group still under construction, which never happens outside the type-definition builders. *) let def_id : Types.ref_index -> Types.Id.t = function | Def id -> id | Rec _ -> assert false (* Resolve a source type reference to its canonical index, for the many contexts that consult an *already-defined* type (function/cont/struct lookups, casts, …). *) let resolve_type_index d ctx idx = let+@ r = resolve_type_ref d ctx idx in def_id r (* Record that [feature] is used and, if it is not enabled, report it at [location]. Validation continues either way (the construct is still typed, for error recovery). *) let require_feature d (ctx : type_context) ~location feature = Wax_utils.Feature.mark_used ctx.features feature; if not (Wax_utils.Feature.is_enabled ctx.features feature) then Error.feature_disabled d ~location feature let heaptype d ctx (h : Ast.Text.heaptype) : heaptype option = match h with | Func -> Some Func | NoFunc -> Some NoFunc | Exn -> Some Exn | NoExn -> Some NoExn | Cont -> Some Cont | NoCont -> Some NoCont | Extern -> Some Extern | NoExtern -> Some NoExtern | Any -> Some Any | Eq -> Some Eq | I31 -> Some I31 | Struct -> Some Struct | Array -> Some Array | None_ -> Some None_ | Type idx -> let+@ ty = resolve_type_index d ctx idx in Type ty | Exact idx -> require_feature d ctx ~location:idx.info Wax_utils.Feature.Custom_descriptors; let+@ ty = resolve_type_index d ctx idx in Exact ty let reftype d ctx { Ast.Text.nullable; typ } = let+@ typ = heaptype d ctx typ in { nullable; typ } let valtype d ctx (ty : Ast.Text.valtype) = match ty with | I32 -> Some I32 | I64 -> Some I64 | F32 -> Some F32 | F64 -> Some F64 | V128 -> Some V128 | Ref r -> let+@ ty = reftype d ctx r in Ref ty let array_map_opt f arr = let exception Short_circuit in try let result = Array.init (Array.length arr) (fun i -> match f arr.(i) with Some v -> v | None -> raise Short_circuit) in Some result with Short_circuit -> None let array_mapi_opt f arr = let exception Short_circuit in try let result = Array.init (Array.length arr) (fun i -> match f i arr.(i) with Some v -> v | None -> raise Short_circuit) in Some result with Short_circuit -> None let functype d ctx { Ast.Text.params; results } = let*@ params = array_map_opt (fun p -> valtype d ctx (snd p.Ast.desc)) params in let+@ results = array_map_opt (fun ty -> valtype d ctx ty) results in { params; results } let muttype f d ctx { mut; typ } = let+@ typ = f d ctx typ in { mut; typ } let tabletype d ctx ({ limits; reftype = typ } : Ast.Text.tabletype) = let+@ reftype = reftype d ctx typ in { Types.Internal.limits = limits.desc; reftype } let globaltype d ctx ty = muttype valtype d ctx ty (* Type-definition builders. These produce the *normalized* representation ([Types.Normalized]) that {!Types.add_rectype} takes: a reference to a member of the group being defined is [Rec pos], anything else is [Def id]. They are deliberately separate from the [Internal]-producing builders above, which serve instruction checking where every reference is already defined. *) let n_heaptype d ctx (h : Ast.Text.heaptype) : Nz.heaptype option = match h with | Func -> Some Nz.Func | NoFunc -> Some Nz.NoFunc | Exn -> Some Nz.Exn | NoExn -> Some Nz.NoExn | Cont -> Some Nz.Cont | NoCont -> Some Nz.NoCont | Extern -> Some Nz.Extern | NoExtern -> Some Nz.NoExtern | Any -> Some Nz.Any | Eq -> Some Nz.Eq | I31 -> Some Nz.I31 | Struct -> Some Nz.Struct | Array -> Some Nz.Array | None_ -> Some Nz.None_ | Type idx -> let+@ r = resolve_type_ref d ctx idx in Nz.Type r | Exact idx -> require_feature d ctx ~location:idx.info Wax_utils.Feature.Custom_descriptors; let+@ r = resolve_type_ref d ctx idx in Nz.Exact r let n_reftype d ctx { Ast.Text.nullable; typ } : Nz.reftype option = let+@ typ = n_heaptype d ctx typ in { Nz.nullable; typ } let n_valtype d ctx (ty : Ast.Text.valtype) : Nz.valtype option = match ty with | I32 -> Some Nz.I32 | I64 -> Some Nz.I64 | F32 -> Some Nz.F32 | F64 -> Some Nz.F64 | V128 -> Some Nz.V128 | Ref r -> let+@ ty = n_reftype d ctx r in Nz.Ref ty let n_functype d ctx { Ast.Text.params; results } : Nz.functype option = let*@ params = array_map_opt (fun p -> n_valtype d ctx (snd p.Ast.desc)) params in let+@ results = array_map_opt (fun ty -> n_valtype d ctx ty) results in { Nz.params; results } let n_storagetype d ctx (ty : Ast.Text.storagetype) : Nz.storagetype option = match ty with | Value ty -> let+@ ty = n_valtype d ctx ty in Nz.Value ty | Packed ty -> Some (Nz.Packed ty) let n_fieldtype d ctx ty : Nz.fieldtype option = muttype n_storagetype d ctx ty let comptype d ctx (ty : Ast.Text.comptype) : Nz.comptype option = match ty with | Func ty -> let+@ ty = n_functype d ctx ty in Nz.Func ty | Struct fields -> let+@ fields = array_map_opt (fun e -> n_fieldtype d ctx (snd e.Ast.desc)) fields in Nz.Struct fields | Array field -> let+@ field = n_fieldtype d ctx field in Nz.Array field | Cont idx -> let+@ r = resolve_type_ref d ctx idx in Nz.Cont r (* A reference is to an already-defined type when it is a [Def], or a [Rec] member strictly before [current] in the group (defined earlier). *) let defined_before current (r : Types.ref_index) = match r with Def _ -> true | Rec pos -> pos < current let subtype d ctx current ({ Ast.Text.typ; supertype; final; descriptor; describes } : Ast.Text.subtype) : Types.Normalized.subtype option = let*@ typ = comptype d ctx typ in let*@ supertype = match supertype with | None -> Some None | Some idx -> let+@ r = resolve_type_ref d ctx idx in (* A supertype must be an already-defined type: reject a self/forward reference to a member of this group at position [>= current]. *) (if not (defined_before current r) then let lst = match idx.desc with | Num _ -> [] | Id id -> Wax_utils.Spell_check.f (fun f -> Hashtbl.iter (fun id' (r, _, _, _) -> if defined_before current r then f id') ctx.label_mapping) id in Error.unbound_index d ~location:idx.info "type" idx lst); Some r in let resolve_opt = function | None -> Some None | Some (idx : Ast.Text.idx) -> require_feature d ctx ~location:idx.info Wax_utils.Feature.Custom_descriptors; let+@ r = resolve_type_ref d ctx idx in Some r in let*@ descriptor = resolve_opt descriptor in let+@ describes = resolve_opt describes in { Nz.typ; supertype; final; descriptor; describes } let rectype d ctx ty = array_mapi_opt (fun i e -> subtype d ctx i (snd e.Ast.desc)) ty let typeuse d ctx (idx, sign) = match (idx, sign) with | Some idx, _ -> ( (* A typeuse always denotes a function type, so reject a reference to a struct/array type with a clean error rather than letting the later [typeuse_functype]/[reference_functype] assert. *) let*@ ty = resolve_type_index d ctx idx in match reference_comptype ctx idx with | Func _ -> Some ty | _ -> Error.expected_func_type d ~location:idx.info idx; None) | _, Some sign -> let+@ ty = n_functype d ctx sign in Types.add_rectype ctx.types [| { typ = Func ty; supertype = None; final = true; descriptor = None; describes = None; }; |] | None, None -> assert false (* Should not happen *) (* Intern the internal representation type of Wax strings — a mutable array of [i8] — and return its canonical index. *) let string_type ctx = Types.add_rectype ctx.types [| { typ = Array { mut = true; typ = Packed I8 }; supertype = None; final = true; descriptor = None; describes = None; }; |] (*** The module context ***) type module_context = { diagnostics : Wax_utils.Diagnostic.context; types : type_context; subtyping_info : Types.subtyping_info; (* Each function carries its type's global index, the source type index it was declared with (when it names one, for a [ref.func]'s rendering), and its source signature (from its declaration, or its referenced type) — so a call names argument and result types from the function's own declaration rather than a shared (deduplicated) type index that another structurally-equal type may own. *) (* Per function: interned type, source type index (if named), signature, and whether [ref.func] on it yields an *exact* reference (true for a defined function or an exact import, false for a plain import). *) functions : (Types.Id.t * Ast.Text.idx option * Ast.Text.functype * bool) Sequence.t; memories : limits Sequence.t; tables : (Types.Internal.tabletype * source_type) Sequence.t; globals : (globaltype * source_type) Sequence.t; (* Each tag carries its type's global index and its source signature, to name a thrown payload's types. *) tags : (Types.Id.t * Ast.Text.functype) Sequence.t; data : unit Sequence.t; (* Each element segment carries its interned reference type and the source reference type from its declaration, to name a mismatched element type. *) elem : (reftype * source_type) Sequence.t; exports : (string, unit) Hashtbl.t; refs : (int, unit) Hashtbl.t; (* Function / global indices referenced anywhere (a call, [ref.func], a [global.get]/[global.set], an export, or the start function) — the marks the [unused-field] warning checks against. *) used_functions : (int, unit) Hashtbl.t; used_globals : (int, unit) Hashtbl.t; (* Each module-defined (non-import) function / global, as (index, source name, report location): the candidates the [unused-field] warning ranges over. *) mutable defined_functions : (int * Ast.Text.name option * Ast.location) list; mutable defined_globals : (int * Ast.Text.name option * Ast.location) list; (* Likewise for imported functions / globals — the [unused-import] candidates. They share the index space (and so the [used_*] marks) with the defined ones, but are reported with a distinct wording. *) mutable imported_functions : (int * Ast.Text.name option * Ast.location) list; mutable imported_globals : (int * Ast.Text.name option * Ast.location) list; (* Whether the extra "unused" analyses run (tied to [-v]/[check], like [unused-local]); consulted by lints emitted during stack validation. *) warn_unused : bool; } module IntSet = Set.Make (Int) type ctx = { (* Each local carries the interned type and the source type for error messages (reconstructed from the interned type if no source is known). *) locals : (valtype * source_type) Sequence.t; (* Each entry is a branch target: its optional label, the interned types a branch carries to it, their source types for error messages, and a flag set when a branch resolves to this frame (used to report labels never branched to). The flag is shared by reference, so a branch deep in a block marks the frame the enclosing instruction created. *) control_types : (string option * valtype array * source_type array * bool ref) list; return_types : valtype array; return_source : source_type array; modul : module_context; mutable initialized_locals : IntSet.t; (* Indices of locals read by a [local.get]. A local that is never read is reported as unused once the function body has been validated. A [ref] (rather than a snapshot field like [initialized_locals]) so a read inside a block propagates up to the function level. *) used_locals : IntSet.t ref; (* Named block labels declared in this function, each with the [bool ref] its control frame carries. A label whose flag is still unset once the body has been validated was never branched to and is reported as unused. *) label_decls : (Ast.Text.name * bool ref) list ref; } let lookup_func_type ctx idx = let ctx = ctx.modul in let*@ ty = resolve_type_index ctx.diagnostics ctx.types idx in let def = Types.get_subtype ctx.subtyping_info ty in match def.typ with | Func f -> Some (ty, f) | Struct _ | Array _ | Cont _ -> Error.expected_func_type ctx.diagnostics ~location:idx.info idx; None let lookup_struct_type ctx idx = let ctx = ctx.modul in let*@ ty, field_map, _, _ = get_type_info ctx.diagnostics ctx.types idx in let ty = def_id ty in let def = Types.get_subtype ctx.subtyping_info ty in match def.typ with | Struct fields -> Some (ty, field_map, fields) | Func _ | Array _ | Cont _ -> Error.expected_struct_type ctx.diagnostics ~location:idx.info idx; None let struct_field_index ctx idx' field_map fields = match idx'.Ast.desc with | Ast.Text.Id id -> ( match List.assoc_opt id field_map with | Some n -> Some n | None -> Error.unknown_field ctx.modul.diagnostics ~location:idx'.Ast.info; None) | Ast.Text.Num n -> let n = Uint32.to_int n in if n < Array.length fields then Some n else ( Error.field_index_out_of_bounds ctx.modul.diagnostics ~location:idx'.Ast.info ~index:n ~count:(Array.length fields); None) let lookup_array_type ctx idx = let ctx = ctx.modul in let*@ ty = resolve_type_index ctx.diagnostics ctx.types idx in let def = Types.get_subtype ctx.subtyping_info ty in match def.typ with | Array field -> Some (ty, field) | Func _ | Struct _ | Cont _ -> Error.expected_array_type ctx.diagnostics ~location:idx.info idx; None (* The descriptor type of the type at global index [ty], for the descriptor instructions ([struct.new_desc], [ref.get_desc], …). Reports an error and returns [None] when the type carries no [descriptor] clause. *) let type_descriptor ctx ~location ty = match (Types.get_subtype ctx.modul.subtyping_info ty).descriptor with | Some desc -> Some desc | None -> Error.type_without_descriptor ctx.modul.diagnostics ~location; None (* The heap type of the descriptor operand expected by a [_desc_eq] cast/branch whose target heap type is [target] (either [Exact x] or [Type x]). The operand's exactness matches the target's ([exact_1] in the spec); [y] is [x]'s descriptor. *) let descriptor_operand_type ctx ~location (target : heaptype) = match target with | Exact x -> let+@ d = type_descriptor ctx ~location x in Exact d | Type x -> let+@ d = type_descriptor ctx ~location x in Type d | _ -> Error.invalid_cast_type ctx.modul.diagnostics ~location; None (* The parameter types of an exception [tag] and, when known, their source types for naming a thrown payload. *) let lookup_tag_type ctx tag = let ctx = ctx.modul in let*@ ty, sign = Sequence.get ctx.diagnostics ctx.tags tag in match (Types.get_subtype ctx.subtyping_info ty).typ with | Struct _ | Array _ | Cont _ -> Error.not_function_type ctx.diagnostics ~location:tag.info; None | Func { params; results } -> if results <> [||] then Error.exception_tag_with_results ctx.diagnostics ~location:tag.info; Some (params, Array.map (fun p -> Plain (snd p.Ast.desc)) sign.params) (* Full function type of a tag, used for stack-switching suspension tags whose results may be non-empty (unlike exception tags). *) let lookup_tag_signature ctx tag = let ctx = ctx.modul in let*@ ty, sign = Sequence.get ctx.diagnostics ctx.tags tag in match (Types.get_subtype ctx.subtyping_info ty).typ with | Func ft -> Some (ft, sign) | Struct _ | Array _ | Cont _ -> Error.not_function_type ctx.diagnostics ~location:tag.info; None (* Resolve a continuation type index to its own index and the function type it wraps. Emits an error if the type is not a continuation type. *) let lookup_cont_type ctx idx = let mctx = ctx.modul in let*@ ty = resolve_type_index mctx.diagnostics mctx.types idx in match (Types.get_subtype mctx.subtyping_info ty).typ with | Cont ft -> ( match (Types.get_subtype mctx.subtyping_info ft).typ with | Func f -> Some (ty, ft, f) | Struct _ | Array _ | Cont _ -> Error.expected_cont_type mctx.diagnostics ~location:idx.info idx; None) | Struct _ | Array _ | Func _ -> Error.expected_cont_type mctx.diagnostics ~location:idx.info idx; None (* The continuation type referenced by a heap type, if any. *) let cont_functype_of_heaptype ctx (h : heaptype) = match h with | Type ty | Exact ty -> ( match (Types.get_subtype ctx.modul.subtyping_info ty).typ with | Cont ft -> ( match (Types.get_subtype ctx.modul.subtyping_info ft).typ with | Func f -> Some f | Struct _ | Array _ | Cont _ -> None) | Func _ | Struct _ | Array _ -> None) | _ -> None (* [functype_matches info ft ft'] holds when [ft] is a subtype of [ft']: parameters are contravariant and results covariant. *) let functype_matches info (ft : functype) (ft' : functype) = Array.length ft.params = Array.length ft'.params && Array.length ft.results = Array.length ft'.results && Array.for_all Fun.id (Array.mapi (fun i p -> Types.val_subtype info ft'.params.(i) p) ft.params) && Array.for_all Fun.id (Array.mapi (fun i r -> Types.val_subtype info r ft'.results.(i)) ft.results) (* [result_subtype info ts ts'] holds when result type [ts] matches [ts'] (same length, covariant element by element). *) let result_subtype info (ts : valtype array) (ts' : valtype array) = Array.length ts = Array.length ts' && Array.for_all Fun.id (Array.mapi (fun i t -> Types.val_subtype info t ts'.(i)) ts) (* [result_equivalent info ts ts'] holds when the two result types are equivalent, i.e. mutual subtypes (which coincides with equality in the single-inheritance reference-type lattice). *) let result_equivalent info ts ts' = result_subtype info ts ts' && result_subtype info ts' ts (* Source type of struct field [n] of the type that reference [idx] names, when the field has a (non-packed) value type. Packed fields surface as i32, so they get no name. Resolving through the reference (not the deduplicated global index) names the field as written at this very type. *) let source_field_valtype ctx idx n : source_type = match reference_comptype ctx.modul.types idx with | Struct fields -> ( let _, (ft : Ast.Text.fieldtype) = fields.(n).Ast.desc in match ft.typ with Value v -> Plain v | Packed _ -> Plain I32) | _ -> assert false (* Source type of the element of the array type that reference [idx] names. *) let source_element_valtype ctx idx : source_type = match reference_comptype ctx.modul.types idx with | Array (ft : Ast.Text.fieldtype) -> ( match ft.typ with Value v -> Plain v | Packed _ -> Plain I32) | _ -> assert false (*** The validation stack ***) (* A stack entry is one of the two bottoms of the validation type lattice or a concrete value. [Bot] is the unknown value of a polymorphic (unreachable) stack: a subtype of every type. [Bot_ref] is the bottom reference type [(ref bot)], produced when a reference-eliminating instruction consumes a [Bot]: it is a subtype of every reference type but of no numeric or vector type, which is what lets [ref.as_non_null] / [br_on_null] reject a numeric use of their result on an otherwise polymorphic stack. [Val] pairs the interned type used for subtype checking with the source type the value was written as, mirroring the Wax side's [inferred_valtype]; the source type is always present, a push that does not supply one reconstructing it from the interned type (naming an indexed type by its canonical index). *) type stack_entry = Bot | Bot_ref | Val of valtype * source_type type stack = | Unreachable | Empty | Cons of Ast.location option * stack_entry * stack (* Returns the popped entry, along with its push location. A pop from an unreachable or empty stack yields the unknown value [Bot]. *) let pop_any ctx loc st = match st with | Unreachable -> (Unreachable, (Bot, None)) | Cons (loc, ty, r) -> (r, (ty, loc)) | Empty -> Error.empty_stack ctx.modul.diagnostics ~location:loc; (st, (Bot, None)) (* The non-null version of a popped reference's source type, for an instruction that re-pushes the value with the null case removed. *) let non_null_source (source : source_type) : source_type = match source with | Plain (Ref r) -> Plain (Ref { r with nullable = false }) | Inline_ref _ as source -> source | _ -> assert false let pop ctx loc ~expected_source ty st = let mismatch location source = match location with | Some location -> Error.instruction_type_mismatch ctx.modul.diagnostics ~location ~consumer:(Some loc) ~provided_source:source ~expected_source | None -> Error.type_mismatch ctx.modul.diagnostics ~location:loc ~provided_source:source ~expected_source in match st with | Unreachable -> (Unreachable, ()) | Cons (_, Bot, r) -> (r, ()) | Cons (location, Bot_ref, r) -> (* [(ref bot)] is a subtype of every reference type but of no numeric or vector type. *) (match ty with Ref _ -> () | _ -> mismatch location Bottom_ref); (r, ()) | Cons (location, Val (ty', source), r) -> let ok = Types.val_subtype ctx.modul.subtyping_info ty' ty in if not ok then mismatch location source; (r, ()) | Empty -> Error.empty_stack ctx.modul.diagnostics ~location:loc; (Unreachable, ()) (* Pop a value whose expected type has no user-written source form — a builtin, address, or abstract reference type — so its rendering is reconstructed. *) let pop_known ctx loc ty = pop ctx loc ~expected_source:(source_of_valtype ty) ty let push_poly loc st = record (Some loc) Polymorphic; (Cons (Some loc, Bot, st), ()) let push_bot_ref loc st = record loc (Pushed Bottom_ref); (Cons (loc, Bot_ref, st), ()) let push ~source loc ty st = record loc (Pushed source); record_value_type_def loc source; (Cons (loc, Val (ty, source), st), ()) (* Push a value whose type has no user-written source form, reconstructing its rendering from the type. *) let push_known loc ty = push ~source:(source_of_valtype ty) loc ty (* The source rendering of a reference to the named type [idx], used as the [source] form of a value an instruction pushes ([named_ref_source], non-null) or expects ([named_ref_null_source], the nullable form pop accepts). *) let named_ref_source idx : source_type = Plain (Ref { nullable = false; typ = Type idx }) let named_ref_null_source idx : source_type = Plain (Ref { nullable = true; typ = Type idx }) (* The push-source of a value a concrete allocator produces at exactly type [idx] ([struct.new], [array.new*], [cont.new], [cont.bind]): these push an *exact* internal reference, so under custom-descriptors the source is rendered exact to match. Without the proposal exact reference types are not expressible, so it falls back to the plain named source (the internal type stays exact, but that extra precision is unobservable there). *) let exact_ref_source ctx idx : source_type = if Wax_utils.Feature.is_enabled ctx.modul.types.features Wax_utils.Feature.Custom_descriptors then Plain (Ref { nullable = false; typ = Exact idx }) else named_ref_source idx (* The source rendering of the descriptor of the type at global index [described] — the [_desc_eq] cast/branch operand (nullable), or the [ref.get_desc] result (non-null, via [~nullable:false]). The descriptor type has no immediate to name it by, so its source name is recovered from [descriptor_source] (recorded at its definition); [exact] matches [described]'s own exactness. *) let descriptor_operand_source ?(nullable = true) tc (described : heaptype) : source_type = let build exact x = match Hashtbl.find_opt tc.descriptor_source x with | Some node -> Plain (Ref { nullable; typ = (if exact then Exact node else Type node) }) | None -> (* No recorded source (a well-formed descriptor type always has one); fall back to the abstract struct supertype rather than a misleading index. *) Plain (Ref { nullable; typ = source_of_heaptype Struct }) in match described with | Exact x -> build true x | Type x -> build false x | _ -> Plain (Ref { nullable; typ = source_of_heaptype Struct }) (* Source-type array for popping the prefix arguments [param_source] followed by a continuation operand of the type named by [x]. *) let cont_operand_source param_source x = Array.append param_source [| named_ref_null_source x |] (* Source params of the function type the continuation type [x] wraps; they are exactly that function type's parameters. *) let cont_param_source ctx x = Array.map (fun p -> Plain (snd p.Ast.desc)) (cont_source_functype ctx.modul.types x).params let unreachable _ = (Unreachable, ()) let return v st = (st, v) (* These operators thread the value stack through validation. [let*] sequences two stack transformers, passing the stack from one to the next. [let*!] and [let*?] guard a transformer on an [option] (typically a failed lookup that has already reported an error): on [None] they abandon this instruction's effect, [let*!] yielding the [unreachable] transformer and [let*?] yielding no stack effect at all (for checks run outside a transformer). *) let ( let* ) e f st = let st, v = e st in f v st let ( let*! ) e f = match e with Some v -> f v | None -> unreachable let ( let*? ) e f = match e with Some v -> f v | None -> () let get_local ctx ?(initialize = false) i = let+@ l = Sequence.get ctx.modul.diagnostics ctx.locals i in let idx = Sequence.get_index ctx.locals i in if initialize then ctx.initialized_locals <- IntSet.add idx ctx.initialized_locals else begin ctx.used_locals := IntSet.add idx !(ctx.used_locals); if not (IntSet.mem idx ctx.initialized_locals) then Error.uninitialized_local ctx.modul.diagnostics ~location:i.info i end; l (* The result nullability of [extern.convert_any] / [any.convert_extern], which propagate the operand's nullability. The operand must be a reference in the [typ] hierarchy: a bottom reference satisfies it as known non-null, a fully-unknown [Bot] is treated as nullable, and a non-reference operand (a reported error) is treated as nullable rather than crashing. *) let convert_operand_nullable ctx loc entry ~typ = match entry with | Bot -> true | Bot_ref -> false | Val (ty, source) -> ( let expected = Ref { nullable = true; typ } in if not (Types.val_subtype ctx.modul.subtyping_info ty expected) then Error.type_mismatch ctx.modul.diagnostics ~location:loc ~provided_source:source ~expected_source:(source_of_valtype expected); match ty with Ref { nullable; _ } -> nullable | _ -> true) let is_defaultable ty = match ty with | I32 | I64 | F32 | F64 | V128 -> true | Ref { nullable; _ } -> nullable let number_or_vec ty = match ty with I32 | I64 | F32 | F64 | V128 -> true | Ref _ -> false let int_un_op_type ty (op : Ast.Text.int_un_op) = match op with | Clz | Ctz | Popcnt | ExtendS _ -> (ty, ty) | Trunc (sz, _) | TruncSat (sz, _) -> ((match sz with `F32 -> F32 | `F64 -> F64), ty) | Reinterpret -> ( (match ty with | I32 -> F32 | I64 -> F64 | _ -> assert false (* Should not happen *)), ty ) | Eqz -> (ty, I32) let int_bin_op_type ty (op : Ast.Text.int_bin_op) = match op with | Add | Sub | Mul | Div _ | Rem _ | And | Or | Xor | Shl | Shr _ | Rotl | Rotr -> ty | Eq | Ne | Lt _ | Gt _ | Le _ | Ge _ -> I32 let float_un_op_type ty (op : Ast.Text.float_un_op) = match op with | Neg | Abs | Ceil | Floor | Trunc | Nearest | Sqrt -> ty | Convert (sz, _) -> ( match sz with `I32 -> I32 | `I64 -> I64) | Reinterpret -> ( match ty with | F32 -> I32 | F64 -> I64 | _ -> assert false (* Should not happen *)) let float_bin_op_type ty (op : Ast.Text.float_bin_op) = match op with | Add | Sub | Mul | Div | Min | Max | CopySign -> ty | Eq | Ne | Lt | Gt | Le | Ge -> I32 (* Returns the interned block parameter and result types, plus their per-element source types (for [pop_args]/[push_results]'s [~source]). *) let blocktype ctx (ty : Ast.Text.blocktype option) = match ty with | None -> Some ([||], [||], [||], [||]) | Some (Typeuse (_, Some ({ params; results } as ft))) -> let*@ iparams = array_map_opt (fun p -> valtype ctx.modul.diagnostics ctx.modul.types (snd p.Ast.desc)) params in let+@ iresults = array_map_opt (valtype ctx.modul.diagnostics ctx.modul.types) results in let param_source, result_source = functype_sources ft in (iparams, iresults, param_source, result_source) | Some (Typeuse (Some idx, None)) -> let+@ _, { params; results } = lookup_func_type ctx idx in let param_source, result_source = functype_sources (reference_functype ctx.modul.types idx) in (params, results, param_source, result_source) | Some (Typeuse (None, None)) -> assert false (* Should not happen *) | Some (Valtype ty) -> let+@ t = valtype ctx.modul.diagnostics ctx.modul.types ty in ([||], [| t |], [||], [| Plain ty |]) let pop_args ctx loc ~source args = let rec loop i = if i < 0 then return () else let* () = pop ctx loc ~expected_source:source.(i) args.(i) in loop (i - 1) in loop (Array.length args - 1) (* [sink] (default [true]) records each pushed result at the instruction span [loc] for the editor type sink. A {e single} result cell also carries [loc] as its provenance — that value was unambiguously pushed by this instruction, so a "value pushed here" diagnostic (and hover) points at it; [push] does the sink recording in that case. Several results cannot each be that one span, so their cells push location [None] and the span is recorded here instead. Set [~sink:false] where [push_results] simulates a branch target or a block's entry parameters rather than pushing the instruction's own results: nothing is attributed to the instruction's span, cells included. *) let push_results ?(sink = true) ~loc ~source results = let single = Array.length results = 1 in let cell_loc = if sink && single then Some loc else None in let rec loop i = if i >= Array.length results then return () else begin if sink && not single then begin record (Some loc) (Pushed source.(i)); record_value_type_def (Some loc) source.(i) end; let* () = push ~source:source.(i) cell_loc results.(i) in loop (i + 1) end in loop 0 let rec output_stack ~full pp st = match st with | Empty -> () | Unreachable -> if full then ( sp_space pp; sp_kw pp "unreachable") | Cons (_, ty, st) -> sp_space pp; (match ty with | Val (_, source) -> print_source_type pp source | Bot -> sp_type pp "bot" | Bot_ref -> sp_box pp (fun () -> sp_punct pp "("; sp_kw pp "ref"; sp_space pp; sp_type pp "bot"; sp_punct pp ")")); output_stack ~full pp st let print_stack st = Wax_utils.Printer.run Format.err_formatter (fun p -> let pp = Wax_utils.Styled_printer.create ~printer:p ~theme:Wax_utils.Colors.no_color ~trivia:(Hashtbl.create 0) () in Wax_utils.Printer.string p "Stack:"; output_stack ~full:true pp st); (st, ()) let _ = print_stack let with_empty_stack ctx location f = let st, () = f Empty in (* The source locations of the values still on the stack, topmost first. Values without a usable location (a block parameter/result, or an error-recovery placeholder) are dropped. *) let rec locations = function | Cons (Some loc, _, st) when loc.Ast.loc_start.Lexing.pos_cnum >= 0 -> loc :: locations st | Cons (_, _, st) -> locations st | Empty | Unreachable -> [] in match st with | Empty | Unreachable -> () | Cons _ -> ( match locations st with | location :: rest -> (* Point a caret right at each leftover value rather than at the (potentially large) enclosing construct. *) let = List.map (fun location -> { Wax_utils.Diagnostic.location; message = Wax_utils.Message.empty; }) rest in Error.leftover_values ctx.diagnostics ~location ~related | [] -> (* No value carries a usable location: point at the construct and list the values that remain, since the location alone does not show them. *) Error.non_empty_stack ctx.diagnostics ~location (fun pp -> output_stack ~full:false pp st)) (*** Instruction-checking helpers ***) (* Check that a list of [provided] argument types matches a list of [expected] parameter types: same length, and each argument a subtype of the corresponding parameter. [descr] names the construct supplying the arguments. Reporting the two lists directly gives a far clearer message than simulating the comparison on the value stack. *) let compare_types ctx ~location ~descr ~provided_source ~expected_source ~provided ~expected () = if Array.length provided <> Array.length expected then Error.argument_count_mismatch ctx.diagnostics ~location ~descr ~provided_source ~expected_source else Array.iteri (fun i p -> let e = expected.(i) in if not (Types.val_subtype ctx.subtyping_info p e) then Error.argument_type_mismatch ctx.diagnostics ~location ~descr ~provided_source:provided_source.(i) ~expected_source:expected_source.(i)) provided let branch_target ctx (idx : Ast.Text.idx) = match idx.desc with | Num i -> ( try let _, params, source, used = List.nth ctx.control_types (Uint32.to_int i) in used := true; Some (params, source) with Failure _ -> Error.unbound_label ctx.modul.diagnostics ~location:idx.Ast.info idx []; None) | Id id -> let rec find l id = match l with | [] -> let lst = Wax_utils.Spell_check.f (fun f -> List.iter (fun (id_opt, _, _, _) -> match id_opt with Some id -> f id | None -> ()) ctx.control_types) id in Error.unbound_label ctx.modul.diagnostics ~location:idx.Ast.info idx lst; None | (Some id', params, source, used) :: _ when id = id' -> used := true; Some (params, source) | _ :: rem -> find rem id in find ctx.control_types id (* The top of the heap-type hierarchy that [t] belongs to (one of [any], [func], [exn], [cont], [extern]). A cast or test pops a reference to this top type — the most general operand the instruction accepts — before checking against the precise target. *) let top_heap_type ctx (t : heaptype) : heaptype = match t with | Any | Eq | I31 | Struct | Array | None_ -> Any | Func | NoFunc -> Func | Exn | NoExn -> Exn | Cont | NoCont -> Cont | Extern | NoExtern -> Extern | Type ty | Exact ty -> ( match (Types.get_subtype ctx.modul.subtyping_info ty).typ with | Struct _ | Array _ -> Any | Func _ -> Func | Cont _ -> Cont) let storage_subtype info ty ty' = match (ty, ty') with | Packed I8, Packed I8 | Packed I16, Packed I16 -> true | Value ty, Value ty' -> Types.val_subtype info ty ty' | Packed I8, Packed I16 | Packed I16, Packed I8 | Packed _, Value _ | Value _, Packed _ -> false let field_subtype info (ty : fieldtype) (ty' : fieldtype) = ty.mut = ty'.mut && storage_subtype info ty.typ ty'.typ && ((not ty.mut) || storage_subtype info ty'.typ ty.typ) (* The reference type difference [t1 \ t2] from the spec: [t1]'s heap type, made non-nullable once a nullable [t2] has consumed the null case. This is the type that falls through a [br_on_cast] (or is sent on by [br_on_cast_fail]); the inline [src_diff] in those arms is the same operation on source types. *) let diff_ref_type t1 t2 = { nullable = t1.nullable && not t2.nullable; typ = t1.typ } (* Whether a branching cast from [ty1] to [ty2] is well-typed. The custom-descriptors proposal relaxes the pre-existing [rt2 <: rt1] to only requiring that [rt1] and [rt2] share a supertype — i.e. lie in the same heap type hierarchy — so under that feature we compare the hierarchy tops. *) let br_cast_compatible ctx (ty1 : reftype) (ty2 : reftype) = if Wax_utils.Feature.is_enabled ctx.modul.types.features Wax_utils.Feature.Custom_descriptors then top_heap_type ctx ty1.typ = top_heap_type ctx ty2.typ else Types.val_subtype ctx.modul.subtyping_info (Ref ty2) (Ref ty1) (* The target of a branching cast ([br_on_cast] and its variants) always carries at least the matched reference to the label, so a zero-result label cannot receive it. [branch_target] alone accepts it — with a polymorphic operand the per-value [pop_args] check below is vacuous — so reject an empty label here. *) let branch_cast_target ctx (idx : Ast.Text.idx) ~location = match branch_target ctx idx with | None -> None | Some (params, source) -> if Array.length params = 0 then ( Error.br_cast_type_mismatch ctx.modul.diagnostics ~location; None) else Some (params, source) (* Run [f] on the current stack and return its result as the monad value while leaving the stack untouched — a peek. [Br_table] uses it to validate every branch target against the same incoming stack. *) let with_current_stack f st = (st, f st) (* Lint a [ref.cast]/[ref.test] against its operand (the top of the current stack). Under single-inheritance subtyping two heap types share a value only when one is a subtype of the other, so unrelated types make the cast always trap (the test always false) — unless a shared [null] slips through. When the operand already has the target type the cast/test is redundant. Only fires when unused reporting is on. *) let lint_cast ctx ~location ~is_test (target : reftype) = if not ctx.modul.warn_unused then return () else with_current_stack (fun st -> match st with | Cons (_, Val (Ref op, _), _) -> let info = ctx.modul.subtyping_info in let = Types.heap_subtype info op.typ target.typ || Types.heap_subtype info target.typ op.typ in if (not related) && not (op.nullable && target.nullable) then Error.cast_always_fails ctx.modul.diagnostics ~location ~is_test else if Types.ref_subtype info op target then Error.redundant_cast ctx.modul.diagnostics ~location ~is_test | _ -> ()) let unpack_type (f : fieldtype) = match f.typ with Value v -> v | Packed _ -> I32 (* Pop [n] values of type [ty] (as [array.new_fixed] does), in time proportional to the operands actually present, not to [n]. Once the stack is [Unreachable] -- the polymorphic base, or a reachable underflow after the first empty pop turns it into one -- every remaining pop trivially succeeds, so stop. This keeps a huge immediate count (e.g. [array.new_fixed 2^31]) from making validation O(n). *) let rec pop_repeat ctx loc ~expected_source ty n st = if n <= 0 then (st, ()) else match st with | Unreachable -> (Unreachable, ()) | _ -> let st, () = pop ctx loc ~expected_source ty st in pop_repeat ctx loc ~expected_source ty (n - 1) st let address_type_to_valtype = function `I32 -> I32 | `I64 -> I64 (* Constants for max offsets *) let max_offset_i32_exclusive = Uint64.of_string "0x1_0000_0000" (* 2^32 *) let max_align = Uint64.of_int 16 let check_memarg ctx location limits sz { Ast.Text.offset; align } = if limits.address_type = `I32 then if Uint64.compare offset max_offset_i32_exclusive >= 0 then Error.memory_offset_too_large ctx.modul.diagnostics ~location max_offset_i32_exclusive; let natural_alignment = match sz with | `I8 -> 1 | `I16 -> 2 | `I32 | `F32 -> 4 | `I64 | `F64 -> 8 | `V128 -> 16 in if Uint64.compare align max_align > 0 || Uint64.to_int align > natural_alignment then Error.memory_align_too_large ctx.modul.diagnostics ~location natural_alignment else match Uint64.to_int align with | 1 | 2 | 4 | 8 | 16 -> () | _ -> Error.bad_memory_align ctx.modul.diagnostics ~location (* An atomic access requires exactly its natural alignment, not merely at most. *) let check_atomic_memarg ctx location limits op { Ast.Text.offset; align } = if limits.address_type = `I32 && Uint64.compare offset max_offset_i32_exclusive >= 0 then Error.memory_offset_too_large ctx.modul.diagnostics ~location max_offset_i32_exclusive; let natural = 1 lsl Atomics.natural_align_log2 op in if Uint64.compare align (Uint64.of_int natural) <> 0 then Error.atomic_alignment ctx.modul.diagnostics ~location natural let memory_instruction_type_and_size ty = match (ty : Ast.Text.num_type) with | NumI32 -> (I32, `I32) | NumF32 -> (F32, `I32) | NumI64 -> (I64, `I64) | NumF64 -> (F64, `I64) let field_has_default (ty : fieldtype) = match ty.typ with | Packed _ -> true | Value ty -> ( match ty with | I32 | I64 | F32 | F64 | V128 -> true | Ref { nullable; _ } -> nullable) let shape_type (shape : Ast.vec_shape) = match shape with | I8x16 | I16x8 | I32x4 -> I32 | I64x2 -> I64 | F32x4 -> F32 | F64x2 -> F64 let check_shape_lanes ctx location (shape : Ast.vec_shape) lane = let max_lane = match shape with | I8x16 -> 16 | I16x8 -> 8 | I32x4 | F32x4 -> 4 | I64x2 | F64x2 -> 2 in if lane >= max_lane then Error.invalid_lane_index ctx.modul.diagnostics ~location max_lane (* Validate the handler clauses of a [resume]/[resume_throw] instruction. [ts2] is the result type of the resumed continuation. *) let check_resume_table ctx loc ts2 clauses = let info = ctx.modul.subtyping_info in List.iter (fun (clause : Ast.Text.on_clause) -> match clause with | OnLabel (tag, label) -> ( match lookup_tag_signature ctx tag with | None -> () | Some ({ params = ts3; results = ts4 }, _) -> ( match branch_target ctx label with | None -> () | Some (ts', _) -> let n = Array.length ts' in let mismatch () = Error.stack_switching_type_mismatch ctx.modul.diagnostics ~location:label.info ~descr: "this handler must take the tag's parameters followed \ by a continuation of the remaining result type" in (* The handler label receives the tag's parameters followed by a continuation of type [cont (ts4 -> ts2)]. *) if n <> Array.length ts3 + 1 then mismatch () else begin Array.iteri (fun i t -> if not (Types.val_subtype info t ts'.(i)) then mismatch ()) ts3; match ts'.(n - 1) with | Ref { typ = ht; _ } -> ( match cont_functype_of_heaptype ctx ht with | Some ft' -> if not (functype_matches info { params = ts4; results = ts2 } ft') then mismatch () | None -> mismatch ()) | _ -> mismatch () end)) | OnSwitch tag -> ( match lookup_tag_signature ctx tag with | None -> () | Some ({ params = ts3; results = ts4 }, _) -> (* A switch handler tag has type [] -> [t*] (no parameters). The canonical stack-switching rule reifies the current continuation as [cont [t2*] -> [t*]] and runs it to this [resume] boundary, whose continuation results are [ts2]; for that to be consistent [t*] must *equal* [ts2] (equivalence, not merely subtyping). A subtype would let a continuation whose completion actually produces [ts2] be observed by a peer at the narrower tag type [t*] — an unchecked narrowing. This matches V8 (IsEquivalentTypeVec) and the spec author's fix; the older written subtyping rule is unsound. *) if Array.length ts3 <> 0 then Error.stack_switching_type_mismatch ctx.modul.diagnostics ~location:loc ~descr:"the tag of a 'switch' handler must take no parameters" else if not (result_equivalent info ts4 ts2) then Error.stack_switching_type_mismatch ctx.modul.diagnostics ~location:loc ~descr: "the results of a 'switch' handler's tag must match the \ resumed continuation's results")) clauses (* Look up an entry in a module-level index space, reporting an unbound-index error (via {!Sequence.get}) when the reference does not resolve. *) let get_memory ctx = Sequence.get ctx.modul.diagnostics ctx.modul.memories let get_table ctx = Sequence.get ctx.modul.diagnostics ctx.modul.tables (* [get_global]/[get_function] are the single resolution points for every [global.get]/[global.set] and [call]/[return_call]/[ref.func] (in a body or a constant expression), so noting the resolved index here records the field as used for the [unused-field] warning. *) let get_global ctx idx = Option.iter (fun i -> Hashtbl.replace ctx.modul.used_globals i ()) (Sequence.get_index_opt ctx.modul.globals idx); Sequence.get ctx.modul.diagnostics ctx.modul.globals idx let get_function ctx idx = Option.iter (fun i -> Hashtbl.replace ctx.modul.used_functions i ()) (Sequence.get_index_opt ctx.modul.functions idx); Sequence.get ctx.modul.diagnostics ctx.modul.functions idx let get_data ctx = Sequence.get ctx.modul.diagnostics ctx.modul.data let get_elem ctx = Sequence.get ctx.modul.diagnostics ctx.modul.elem (* Pop a memory/table address operand, whose width follows the address type. *) let pop_address ctx loc limits = pop_known ctx loc (address_type_to_valtype limits.address_type) (*** The instruction validator ***) (* The usage flag to give a block form's control frame(s). A named label is also recorded in [ctx.label_decls] so an un-branched-to one can be reported once the body is validated; the same flag is shared across an [if]'s two arms and a [try]'s several bodies, which reuse one source label. *) let track_label ctx label = let used = ref false in Option.iter (fun l -> ctx.label_decls := (l, used) :: !(ctx.label_decls)) label; used let rec instruction_core ctx (i : _ Ast.Text.instr) = if false then Format.eprintf "%a@." print_instr i; let loc = i.info in match i.desc with | Block { label; typ; block = b } -> let*! params, results, param_source, result_source = blocktype ctx typ in let* () = pop_args ctx loc ~source:param_source params in let used = track_label ctx label in block ctx loc label ~used ~param_source ~result_source ~br_source:result_source ~params ~results ~br_params:results b.desc; push_results ~loc ~source:result_source results | Loop { label; typ; block = b } -> let*! params, results, param_source, result_source = blocktype ctx typ in let* () = pop_args ctx loc ~source:param_source params in let used = track_label ctx label in block ctx loc label ~used ~param_source ~result_source ~br_source:param_source ~params ~results ~br_params:params b.desc; push_results ~loc ~source:result_source results | If { label; typ; if_block; else_block } -> let*! params, results, param_source, result_source = blocktype ctx typ in let* () = pop_known ctx loc I32 in let* () = pop_args ctx loc ~source:param_source params in let used = track_label ctx label in block ctx loc label ~used ~param_source ~result_source ~br_source:result_source ~params ~results ~br_params:results if_block.desc; block ctx loc label ~used ~param_source ~result_source ~br_source:result_source ~params ~results ~br_params:results else_block.desc; push_results ~loc ~source:result_source results | TryTable { label; typ; block = b; catches } -> let*! params, results, param_source, result_source = blocktype ctx typ in let* () = pop_args ctx loc ~source:param_source params in let used = track_label ctx label in block ctx loc label ~used ~param_source ~result_source ~br_source:result_source ~params ~results ~br_params:results b.desc; List.iter (fun (catch : Ast.Text.catch) -> match catch with | Catch (tag, label) -> let*? args, arg_source = lookup_tag_type ctx tag in let*? params, param_source = branch_target ctx label in compare_types ctx.modul ~location:loc ~descr:"this exception handler" ~provided_source:arg_source ~expected_source:param_source ~provided:args ~expected:params () | CatchRef (tag, label) -> let*? args, arg_source = lookup_tag_type ctx tag in let*? params, param_source = branch_target ctx label in let provided = Array.append args [| Ref { nullable = false; typ = Exn } |] in let provided_source = Array.append arg_source [| Plain (Ref { nullable = false; typ = Exn }) |] in compare_types ctx.modul ~location:loc ~descr:"this exception handler" ~provided_source ~expected_source:param_source ~provided ~expected:params () | CatchAll label -> Option.iter (fun (params, param_source) -> compare_types ctx.modul ~location:loc ~descr:"this exception handler" ~provided_source:[||] ~expected_source:param_source ~provided:[||] ~expected:params ()) (branch_target ctx label) | CatchAllRef label -> Option.iter (fun (params, param_source) -> compare_types ctx.modul ~location:loc ~descr:"this exception handler" ~provided_source: [| Plain (Ref { nullable = false; typ = Exn }) |] ~expected_source:param_source ~provided:[| Ref { nullable = false; typ = Exn } |] ~expected:params ()) (branch_target ctx label)) catches; push_results ~loc ~source:result_source results | Try { label; typ; block = b; catches; catch_all } -> let*! params, results, param_source, result_source = blocktype ctx typ in let* () = pop_args ctx loc ~source:param_source params in let used = track_label ctx label in block ctx loc label ~used ~param_source ~result_source ~br_source:result_source ~params ~results ~br_params:results b.desc; List.iter (fun (tag, b) -> let*? params', param_source = lookup_tag_type ctx tag in block ctx loc label ~used ~param_source ~result_source ~br_source:result_source ~params:params' ~results ~br_params:results b.Ast.desc) catches; Option.iter (fun b -> block ctx loc label ~used ~param_source ~result_source ~br_source:result_source ~params ~results ~br_params:results b.Ast.desc) catch_all; push_results ~loc ~source:result_source results | Unreachable -> unreachable | Nop -> return () | Throw idx -> let*! params, param_source = lookup_tag_type ctx idx in let* () = pop_args ctx loc ~source:param_source params in unreachable | ThrowRef -> let* () = pop_known ctx loc (Ref { nullable = true; typ = Exn }) in unreachable | ContNew x -> let*! ty, ft, _ = lookup_cont_type ctx x in let func_source = match reference_comptype ctx.modul.types x with | Cont r -> named_ref_null_source r | _ -> assert false in let* () = pop ctx loc ~expected_source:func_source (Ref { nullable = true; typ = Type ft }) in push ~source:(exact_ref_source ctx x) (Some loc) (Ref { nullable = false; typ = Exact ty }) | ContBind (x, y) -> let*! xty, _, ftx = lookup_cont_type ctx x in let*! yty, _, fty = lookup_cont_type ctx y in let n1 = Array.length ftx.params in let n1' = Array.length fty.params in if n1 < n1' then ( Error.stack_switching_type_mismatch ctx.modul.diagnostics ~location:loc ~descr: "the resulting continuation takes more parameters than the \ original one"; unreachable) else begin let ts11 = Array.sub ftx.params 0 (n1 - n1') in let ts12 = Array.sub ftx.params (n1 - n1') n1' in if not (functype_matches ctx.modul.subtyping_info { params = ts12; results = ftx.results } fty) then ( Error.stack_switching_type_mismatch ctx.modul.diagnostics ~location:loc ~descr: "the bound parameters and results do not match between the two \ continuation types"; unreachable) else begin let* () = pop_args ctx loc ~source: (cont_operand_source (Array.sub (cont_param_source ctx x) 0 (n1 - n1')) x) (Array.append ts11 [| Ref { nullable = true; typ = Type xty } |]) in push ~source:(exact_ref_source ctx y) (Some loc) (Ref { nullable = false; typ = Exact yty }) end end | Suspend x -> let*! { params = ts1; results = ts2 }, sign = lookup_tag_signature ctx x in let param_source, result_source = functype_sources sign in let* () = pop_args ctx loc ~source:param_source ts1 in push_results ~loc ~source:result_source ts2 | Resume (x, clauses) -> let*! xty, _, ftx = lookup_cont_type ctx x in check_resume_table ctx loc ftx.results clauses; let _, result_source = functype_sources (cont_source_functype ctx.modul.types x) in let* () = pop_args ctx loc ~source:(cont_operand_source (cont_param_source ctx x) x) (Array.append ftx.params [| Ref { nullable = true; typ = Type xty } |]) in push_results ~loc ~source:result_source ftx.results | ResumeThrow (x, y, clauses) -> let*! xty, _, ftx = lookup_cont_type ctx x in let*! { params = ts0; _ }, sign = lookup_tag_signature ctx y in check_resume_table ctx loc ftx.results clauses; let _, result_source = functype_sources (cont_source_functype ctx.modul.types x) in let* () = pop_args ctx loc ~source:(cont_operand_source (fst (functype_sources sign)) x) (Array.append ts0 [| Ref { nullable = true; typ = Type xty } |]) in push_results ~loc ~source:result_source ftx.results | ResumeThrowRef (x, clauses) -> let*! xty, _, ftx = lookup_cont_type ctx x in check_resume_table ctx loc ftx.results clauses; let _, result_source = functype_sources (cont_source_functype ctx.modul.types x) in let* () = pop_args ctx loc ~source: [| Plain (Ref { nullable = true; typ = Exn }); named_ref_null_source x; |] [| Ref { nullable = true; typ = Exn }; Ref { nullable = true; typ = Type xty }; |] in push_results ~loc ~source:result_source ftx.results | Switch (x, y) -> let*! xty, _, ftx = lookup_cont_type ctx x in let ts11 = ftx.params in let n = Array.length ts11 in let inner = match if n = 0 then None else Some ts11.(n - 1) with | Some (Ref { typ = ht; _ }) -> cont_functype_of_heaptype ctx ht | _ -> None in let*! inner_ft = match inner with | Some _ -> inner | None -> Error.stack_switching_type_mismatch ctx.modul.diagnostics ~location:loc ~descr: "the continuation's last parameter must itself be a \ continuation type"; None in let*! { params = ts31; results = t }, _ = lookup_tag_signature ctx y in let info = ctx.modul.subtyping_info in if Array.length ts31 <> 0 || (not (result_subtype info ftx.results t)) || not (result_subtype info t inner_ft.results) then ( Error.stack_switching_type_mismatch ctx.modul.diagnostics ~location:loc ~descr: "the 'switch' tag must take no parameters and its results must \ match the two continuation types"; unreachable) else begin (* The inner continuation is named by [x]'s last parameter, so its parameters' source types are that continuation's source params. *) let ts21 = inner_ft.params in let ts21_text = (* [inner] is [Some] only when [x]'s last parameter is a concrete continuation reference, so its source form is [(ref $idx)]. *) match (cont_param_source ctx x).(n - 1) with | Plain (Ref { typ = Type idx; _ }) -> cont_param_source ctx idx | _ -> assert false in let ts11' = Array.sub ts11 0 (n - 1) in let ts11'_text = Array.sub (cont_param_source ctx x) 0 (n - 1) in let* () = pop_args ctx loc ~source:(cont_operand_source ts11'_text x) (Array.append ts11' [| Ref { nullable = true; typ = Type xty } |]) in push_results ~loc ~source:ts21_text ts21 end | Br idx -> let*! params, param_source = branch_target ctx idx in let* () = pop_args ctx loc ~source:param_source params in unreachable | Br_if idx -> let* () = pop_known ctx loc I32 in let*! params, param_source = branch_target ctx idx in let* () = pop_args ctx loc ~source:param_source params in push_results ~sink:false ~loc ~source:param_source params (* Branch-hinting proposal: the wrapper is advisory and has the exact stack effect of the branch it wraps. The hint is only allowed on a conditional branch ([if]/[br_if]/[br_on_*], through a folded wrapper); reject it anywhere else. *) | Hinted (_, inner) -> let rec is_branch_hint_target (d : _ Ast.Text.instr_desc) = match d with | If _ | Br_if _ | Br_on_null _ | Br_on_non_null _ | Br_on_cast _ | Br_on_cast_fail _ | Br_on_cast_desc_eq _ | Br_on_cast_desc_eq_fail _ -> true | Folded (b, _) -> is_branch_hint_target b.Ast.desc | _ -> false in if not (is_branch_hint_target inner.Ast.desc) then Error.branch_hint_invalid_target ctx.modul.diagnostics ~location:loc; instruction ctx inner | Br_table (lst, idx) -> let* () = pop_known ctx loc I32 in let*! params, _ = branch_target ctx idx in let len = Array.length params in let* () = with_current_stack (fun st -> List.iter (fun idx' -> let*? params, param_source = branch_target ctx idx' in let len' = Array.length params in if len <> len' then Error.branch_parameter_count_mismatch ctx.modul.diagnostics ~location:loc idx len idx' len' else ignore (pop_args ctx loc ~source:param_source params st)) (idx :: lst)) in unreachable | Br_on_null idx -> ( let* ty, loc' = pop_any ctx loc in (* The branch carries the label's parameters; the value falls through with its null case removed ([(ref bot)] when the operand was a bottom). *) let fallthrough push_top = let*! params, param_source = branch_target ctx idx in let* () = pop_args ctx loc ~source:param_source params in let* () = push_results ~sink:false ~loc ~source:param_source params in push_top in match ty with | Bot | Bot_ref -> fallthrough (push_bot_ref (Some loc)) | Val (Ref { nullable = _; typ }, source) -> fallthrough (push ~source:(non_null_source source) (Some loc) (Ref { nullable = false; typ })) | Val (_, source) -> Error.expected_ref_type ctx.modul.diagnostics ~location:loc ~src_loc:loc' ~source; unreachable) | Br_on_non_null idx -> ( let* ty, loc' = pop_any ctx loc in (* The branch carries the label's parameters ending in the non-null reference ([(ref bot)] for a bottom operand); the value is consumed on fall-through. *) let to_branch push_ref = let* () = push_ref in let*! params, param_source = branch_target ctx idx in (* [br_on_non_null] requires the target label to be [t* (ref ht)]: the pushed non-null reference is consumed by that trailing type. An empty label has no such type, so [pop_args] would silently accept it. *) if Array.length params = 0 then ( Error.br_on_non_null_no_ref ctx.modul.diagnostics ~location:loc; unreachable) else let* () = pop_args ctx loc ~source:param_source params in let* () = push_results ~sink:false ~loc ~source:param_source params in let* _ = pop_any ctx loc in return () in match ty with | Bot | Bot_ref -> to_branch (push_bot_ref None) | Val (Ref { nullable = _; typ }, source) -> to_branch (push ~source:(non_null_source source) None (Ref { nullable = false; typ })) | Val (_, source) -> Error.expected_ref_type ctx.modul.diagnostics ~location:loc ~src_loc:loc' ~source; unreachable) | Br_on_cast (idx, ty1, ty2) -> let src_ty1 = Plain (Ast.Text.Ref ty1) and src_ty2 = Plain (Ast.Text.Ref ty2) in (* The value that falls through has [ty1]'s heap type, non-null once a nullable [ty2] has consumed the null case. *) let src_diff = Plain (Ast.Text.Ref { nullable = ty1.nullable && not ty2.nullable; typ = ty1.typ }) in let*! ty1 = reftype ctx.modul.diagnostics ctx.modul.types ty1 in let*! ty2 = reftype ctx.modul.diagnostics ctx.modul.types ty2 in (match (top_heap_type ctx ty1.typ, top_heap_type ctx ty2.typ) with | Cont, _ | _, Cont -> Error.invalid_cast_type ctx.modul.diagnostics ~location:loc | _ -> ()); if not (br_cast_compatible ctx ty1 ty2) then Error.br_cast_type_mismatch ctx.modul.diagnostics ~location:loc; let* () = pop ctx loc ~expected_source:src_ty1 (Ref ty1) in let* () = push ~source:src_ty2 None (Ref ty2) in let*! params, param_source = branch_cast_target ctx idx ~location:loc in let* () = pop_args ctx loc ~source:param_source params in let* () = push_results ~sink:false ~loc ~source:param_source params in let* _ = pop_any ctx loc in push ~source:src_diff (Some loc) (Ref (diff_ref_type ty1 ty2)) | Br_on_cast_fail (idx, ty1, ty2) -> let src_ty1 = Plain (Ast.Text.Ref ty1) and src_ty2 = Plain (Ast.Text.Ref ty2) in (* The value sent to the branch has [ty1]'s heap type, non-null once a nullable [ty2] has consumed the null case. *) let src_diff = Plain (Ast.Text.Ref { nullable = ty1.nullable && not ty2.nullable; typ = ty1.typ }) in let*! ty1 = reftype ctx.modul.diagnostics ctx.modul.types ty1 in let*! ty2 = reftype ctx.modul.diagnostics ctx.modul.types ty2 in (match (top_heap_type ctx ty1.typ, top_heap_type ctx ty2.typ) with | Cont, _ | _, Cont -> Error.invalid_cast_type ctx.modul.diagnostics ~location:loc | _ -> ()); if not (br_cast_compatible ctx ty1 ty2) then Error.br_cast_type_mismatch ctx.modul.diagnostics ~location:loc; let* () = pop ctx loc ~expected_source:src_ty1 (Ref ty1) in let* () = push ~source:src_diff None (Ref (diff_ref_type ty1 ty2)) in let*! params, param_source = branch_cast_target ctx idx ~location:loc in let* () = pop_args ctx loc ~source:param_source params in let* () = push_results ~sink:false ~loc ~source:param_source params in let* _ = pop_any ctx loc in push ~source:src_ty2 (Some loc) (Ref ty2) | Br_on_cast_desc_eq (idx, ty1, ty2) -> (* As [br_on_cast], preceded by consuming a descriptor operand whose exactness matches the target [ty2]. *) let src_ty1 = Plain (Ast.Text.Ref ty1) and src_ty2 = Plain (Ast.Text.Ref ty2) in let src_diff = Plain (Ast.Text.Ref { nullable = ty1.nullable && not ty2.nullable; typ = ty1.typ }) in let*! ty1 = reftype ctx.modul.diagnostics ctx.modul.types ty1 in let*! ty2 = reftype ctx.modul.diagnostics ctx.modul.types ty2 in (match (top_heap_type ctx ty1.typ, top_heap_type ctx ty2.typ) with | Cont, _ | _, Cont -> Error.invalid_cast_type ctx.modul.diagnostics ~location:loc | _ -> ()); if not (br_cast_compatible ctx ty1 ty2) then Error.br_cast_type_mismatch ctx.modul.diagnostics ~location:loc; let*! desc_ht = descriptor_operand_type ctx ~location:loc ty2.typ in let* () = pop ctx loc ~expected_source:(descriptor_operand_source ctx.modul.types ty2.typ) (Ref { nullable = true; typ = desc_ht }) in let* () = pop ctx loc ~expected_source:src_ty1 (Ref ty1) in let* () = push ~source:src_ty2 None (Ref ty2) in let*! params, param_source = branch_cast_target ctx idx ~location:loc in let* () = pop_args ctx loc ~source:param_source params in let* () = push_results ~sink:false ~loc ~source:param_source params in let* _ = pop_any ctx loc in push ~source:src_diff (Some loc) (Ref (diff_ref_type ty1 ty2)) | Br_on_cast_desc_eq_fail (idx, ty1, ty2) -> let src_ty1 = Plain (Ast.Text.Ref ty1) and src_ty2 = Plain (Ast.Text.Ref ty2) in let src_diff = Plain (Ast.Text.Ref { nullable = ty1.nullable && not ty2.nullable; typ = ty1.typ }) in let*! ty1 = reftype ctx.modul.diagnostics ctx.modul.types ty1 in let*! ty2 = reftype ctx.modul.diagnostics ctx.modul.types ty2 in (match (top_heap_type ctx ty1.typ, top_heap_type ctx ty2.typ) with | Cont, _ | _, Cont -> Error.invalid_cast_type ctx.modul.diagnostics ~location:loc | _ -> ()); if not (br_cast_compatible ctx ty1 ty2) then Error.br_cast_type_mismatch ctx.modul.diagnostics ~location:loc; let*! desc_ht = descriptor_operand_type ctx ~location:loc ty2.typ in let* () = pop ctx loc ~expected_source:(descriptor_operand_source ctx.modul.types ty2.typ) (Ref { nullable = true; typ = desc_ht }) in let* () = pop ctx loc ~expected_source:src_ty1 (Ref ty1) in let* () = push ~source:src_diff None (Ref (diff_ref_type ty1 ty2)) in let*! params, param_source = branch_cast_target ctx idx ~location:loc in let* () = pop_args ctx loc ~source:param_source params in let* () = push_results ~sink:false ~loc ~source:param_source params in let* _ = pop_any ctx loc in push ~source:src_ty2 (Some loc) (Ref ty2) | Return -> let* () = pop_args ctx loc ~source:ctx.return_source ctx.return_types in unreachable | Call idx -> ( let*! ty, _, sign, _ = get_function ctx idx in match (Types.get_subtype ctx.modul.subtyping_info ty).typ with | Struct _ | Array _ | Cont _ -> Error.expected_func_type ctx.modul.diagnostics ~location:loc idx; unreachable | Func { params; results } -> let param_source, result_source = functype_sources sign in (* Give the callee identifier the function's signature on hover. *) record (Some idx.info) (Signature (param_source, result_source)); let* () = pop_args ctx loc ~source:param_source params in push_results ~loc ~source:result_source results) | CallRef idx -> let*! type_idx, { params; results } = lookup_func_type ctx idx in let param_source, result_source = functype_sources (reference_functype ctx.modul.types idx) in let* () = pop ctx loc ~expected_source:(named_ref_null_source idx) (Ref { nullable = true; typ = Type type_idx }) in let* () = pop_args ctx loc ~source:param_source params in push_results ~loc ~source:result_source results | CallIndirect (idx, tu) -> ( let*! typ, table_source = get_table ctx idx in let*! ty = typeuse ctx.modul.diagnostics ctx.modul.types tu in if not (Types.val_subtype ctx.modul.subtyping_info (Ref typ.reftype) (Ref { nullable = true; typ = Func })) then ( Error.table_type_mismatch ctx.modul.diagnostics ~location:loc ~source:table_source idx; unreachable) else match (Types.get_subtype ctx.modul.subtyping_info ty).typ with | Struct _ | Array _ | Cont _ -> Error.expected_func_type ctx.modul.diagnostics ~location:loc idx; unreachable | Func { params; results } -> let param_source, result_source = functype_sources (typeuse_functype ctx.modul.types tu) in let* () = pop_address ctx loc typ.limits in let* () = pop_args ctx loc ~source:param_source params in push_results ~loc ~source:result_source results) | ReturnCall idx -> ( let*! ty, _, sign, _ = get_function ctx idx in match (Types.get_subtype ctx.modul.subtyping_info ty).typ with | Struct _ | Array _ | Cont _ -> Error.expected_func_type ctx.modul.diagnostics ~location:loc idx; unreachable | Func { params; results } -> let param_source, result_source = functype_sources sign in record (Some idx.info) (Signature (param_source, result_source)); let* () = pop_args ctx loc ~source:param_source params in compare_types ctx.modul ~location:loc ~descr:"this tail call" ~provided_source:result_source ~expected_source:ctx.return_source ~provided:results ~expected:ctx.return_types (); unreachable) | ReturnCallRef idx -> let*! type_idx, { params; results } = lookup_func_type ctx idx in let param_source, result_source = functype_sources (reference_functype ctx.modul.types idx) in let* () = pop ctx loc ~expected_source:(named_ref_null_source idx) (Ref { nullable = true; typ = Type type_idx }) in let* () = pop_args ctx loc ~source:param_source params in compare_types ctx.modul ~location:loc ~descr:"this tail call" ~provided_source:result_source ~expected_source:ctx.return_source ~provided:results ~expected:ctx.return_types (); unreachable | ReturnCallIndirect (idx, tu) -> ( let*! typ, table_source = get_table ctx idx in let*! ty = typeuse ctx.modul.diagnostics ctx.modul.types tu in if not (Types.val_subtype ctx.modul.subtyping_info (Ref typ.reftype) (Ref { nullable = true; typ = Func })) then ( Error.table_type_mismatch ctx.modul.diagnostics ~location:loc ~source:table_source idx; unreachable) else match (Types.get_subtype ctx.modul.subtyping_info ty).typ with | Struct _ | Array _ | Cont _ -> Error.expected_func_type ctx.modul.diagnostics ~location:loc idx; unreachable | Func { params; results } -> let param_source, result_source = functype_sources (typeuse_functype ctx.modul.types tu) in let* () = pop_address ctx loc typ.limits in let* () = pop_args ctx loc ~source:param_source params in compare_types ctx.modul ~location:loc ~descr:"this tail call" ~provided_source:result_source ~expected_source:ctx.return_source ~provided:results ~expected:ctx.return_types (); unreachable) | Drop -> let* _ = pop_any ctx loc in return () | Select None -> ( let* () = pop_known ctx loc I32 in let* ty1, loc1 = pop_any ctx loc in let* ty2, loc2 = pop_any ctx loc in (* A bare [select] forbids reference operands; the bottom reference, like any reference, is rejected here. Each operand reduces to its value ([None] when unknown), so the cases below mirror the operand stack. *) let as_operand = function | Bot -> None | Bot_ref -> Error.expected_number_or_vec ctx.modul.diagnostics ~location:loc ~source:Bottom_ref; None | Val (ty, source) -> Some (ty, source) in match (as_operand ty1, as_operand ty2) with | None, None -> push_poly loc | Some (ty1, source1), Some (ty2, source2) -> if not (number_or_vec ty1) then Error.expected_number_or_vec ctx.modul.diagnostics ~location:loc ~source:source1; if not (number_or_vec ty2) then Error.expected_number_or_vec ctx.modul.diagnostics ~location:loc ~source:source2; if ty1 <> ty2 then Error.select_type_mismatch ctx.modul.diagnostics ~location:loc ~loc1 ~source1 ~loc2 ~source2; push ~source:source1 (Some loc) ty1 | Some (ty, source), None | None, Some (ty, source) -> if not (number_or_vec ty) then Error.expected_number_or_vec ctx.modul.diagnostics ~location:loc ~source; push ~source (Some loc) ty) | Select (Some lst) -> ( match lst with | [ typ ] -> let src_typ = Plain typ in let*! typ = valtype ctx.modul.diagnostics ctx.modul.types typ in let* () = pop_known ctx loc I32 in let* () = pop ctx loc ~expected_source:src_typ typ in let* () = pop ctx loc ~expected_source:src_typ typ in push ~source:src_typ (Some loc) typ | _ -> Error.select_result_count ctx.modul.diagnostics ~location:loc; pop_known ctx loc I32) | LocalGet i -> let*! ty, source = get_local ctx i in push ~source (Some loc) ty | LocalSet i -> let*! ty, source = get_local ~initialize:true ctx i in (* Give the identifier the local's type, so hover over [$x] shows it even though [local.set] itself leaves nothing on the stack. *) record (Some i.info) (Pushed source); pop ctx loc ~expected_source:source ty | LocalTee i -> let*! ty, source = get_local ~initialize:true ctx i in record (Some i.info) (Pushed source); let* () = pop ctx loc ~expected_source:source ty in push ~source (Some loc) ty | GlobalGet idx -> let*! ty, source = get_global ctx idx in push ~source (Some loc) ty.typ | GlobalSet idx -> let*! ty, source = get_global ctx idx in record (Some idx.info) (Pushed source); if not ty.mut then Error.immutable_global ctx.modul.diagnostics ~location:loc idx; pop ctx loc ~expected_source:source ty.typ | Load (idx, memarg, ty) -> let*! limits = get_memory ctx idx in let ty, sz = memory_instruction_type_and_size ty in check_memarg ctx loc limits sz memarg; let* () = pop_address ctx loc limits in push ~source:(source_of_valtype ty) (Some loc) ty | LoadS (idx, memarg, ty, sz, _) -> let*! limits = get_memory ctx idx in let ty = match ty with `I32 -> I32 | `I64 -> I64 in check_memarg ctx loc limits (sz :> [ `I8 | `I16 | `I32 | `I64 | `V128 ]) memarg; let* () = pop_address ctx loc limits in push ~source:(source_of_valtype ty) (Some loc) ty | Store (idx, memarg, ty) -> let*! limits = get_memory ctx idx in let ty, sz = memory_instruction_type_and_size ty in check_memarg ctx loc limits sz memarg; let* () = pop_known ctx loc ty in let* () = pop_address ctx loc limits in return () | StoreS (idx, memarg, ty, sz) -> let*! limits = get_memory ctx idx in let ty = match ty with `I32 -> I32 | `I64 -> I64 in check_memarg ctx loc limits (sz :> [ `I8 | `I16 | `I32 | `I64 | `V128 ]) memarg; let* () = pop_known ctx loc ty in pop_address ctx loc limits | Atomic (idx, op, memarg) -> let*! limits = get_memory ctx idx in check_atomic_memarg ctx loc limits op memarg; let vt = function `I32 -> I32 | `I64 -> I64 in let operands, results = Atomics.signature op in (* Operands sit above the address, topmost last, so pop in reverse. *) let* () = List.fold_left (fun acc t -> let* () = acc in pop_known ctx loc (vt t)) (return ()) (List.rev operands) in let* () = pop_address ctx loc limits in List.fold_left (fun acc t -> let* () = acc in push_known (Some loc) (vt t)) (return ()) results | AtomicFence -> return () | MemorySize idx -> let*! limits = get_memory ctx idx in let ty = address_type_to_valtype limits.address_type in push ~source:(source_of_valtype ty) (Some loc) ty | MemoryGrow idx -> let*! limits = get_memory ctx idx in let addr_ty = address_type_to_valtype limits.address_type in let* () = pop_known ctx loc addr_ty in push ~source:(source_of_valtype addr_ty) (Some loc) addr_ty | MemoryFill idx -> let*! limits = get_memory ctx idx in let addr_ty = address_type_to_valtype limits.address_type in let* () = pop_known ctx loc addr_ty in let* () = pop_known ctx loc I32 in pop_known ctx loc addr_ty | MemoryCopy (idx, idx') -> let*! limits = get_memory ctx idx in let*! limits' = get_memory ctx idx' in (* The length operand uses the smaller of the two address types: i32 if either memory is 32-bit, i64 only if both are 64-bit. *) let address_type = match (limits.address_type, limits'.address_type) with | `I32, _ | _, `I32 -> `I32 | `I64, `I64 -> `I64 in let addr_ty = address_type_to_valtype limits.address_type in let addr_ty' = address_type_to_valtype limits'.address_type in let addr_ty'' = address_type_to_valtype address_type in let* () = pop_known ctx loc addr_ty'' in let* () = pop_known ctx loc addr_ty' in pop_known ctx loc addr_ty | MemoryInit (idx, idx') -> let*! limits = get_memory ctx idx in ignore (get_data ctx idx'); let addr_ty = address_type_to_valtype limits.address_type in let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc I32 in pop_known ctx loc addr_ty | DataDrop idx -> ignore (get_data ctx idx); return () | VecBinOp _ -> let* () = pop_known ctx loc V128 in let* () = pop_known ctx loc V128 in push_known (Some loc) V128 | VecConst _ -> push_known (Some loc) V128 | VecUnOp _ -> let* () = pop_known ctx loc V128 in push_known (Some loc) V128 | VecTest _ -> let* () = pop_known ctx loc V128 in push_known (Some loc) I32 | VecShift _ -> let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc V128 in push_known (Some loc) V128 | VecBitmask _ -> let* () = pop_known ctx loc V128 in push_known (Some loc) I32 | VecTernOp _ -> let* () = pop_known ctx loc V128 in let* () = pop_known ctx loc V128 in let* () = pop_known ctx loc V128 in push_known (Some loc) V128 | VecBitselect -> let* () = pop_known ctx loc V128 in let* () = pop_known ctx loc V128 in let* () = pop_known ctx loc V128 in push_known (Some loc) V128 | VecSplat shape -> let ty = shape_type shape in let* () = pop_known ctx loc ty in push_known (Some loc) V128 | VecLoad (idx, sz, memarg) -> let*! limits = get_memory ctx idx in check_memarg ctx loc limits (match sz with | Load128 -> `V128 | Load8x8S | Load8x8U | Load16x4S | Load16x4U | Load32x2S | Load32x2U | Load64Zero -> `I64 | Load32Zero -> `I32) memarg; let* () = pop_address ctx loc limits in push_known (Some loc) V128 | VecStore (idx, memarg) -> let*! limits = get_memory ctx idx in check_memarg ctx loc limits `V128 memarg; let* () = pop_known ctx loc V128 in let* () = pop_address ctx loc limits in return () | VecLoadLane (idx, op, mem, lane) -> let*! limits = get_memory ctx idx in check_memarg ctx loc limits (op :> [ `I8 | `I16 | `I32 | `I64 | `F32 | `F64 | `V128 ]) mem; let sz = match op with `I8 -> 1 | `I16 -> 2 | `I32 -> 4 | `I64 -> 8 in if lane >= 16 / sz then Error.invalid_lane_index ctx.modul.diagnostics ~location:loc (16 / sz); let* () = pop_known ctx loc V128 in let* () = pop_address ctx loc limits in push_known (Some loc) V128 | VecStoreLane (idx, op, mem, lane) -> let*! limits = get_memory ctx idx in check_memarg ctx loc limits (op :> [ `I8 | `I16 | `I32 | `I64 | `F32 | `F64 | `V128 ]) mem; let sz = match op with `I8 -> 1 | `I16 -> 2 | `I32 -> 4 | `I64 -> 8 in if lane >= 16 / sz then Error.invalid_lane_index ctx.modul.diagnostics ~location:loc (16 / sz); let* () = pop_known ctx loc V128 in let* () = pop_address ctx loc limits in return () | VecLoadSplat (idx, op, mem) -> let*! limits = get_memory ctx idx in check_memarg ctx loc limits (op :> [ `I8 | `I16 | `I32 | `I64 | `F32 | `F64 | `V128 ]) mem; let* () = pop_address ctx loc limits in push_known (Some loc) V128 | VecExtract (shape, _, lane) -> check_shape_lanes ctx loc shape lane; let* () = pop_known ctx loc V128 in push_known (Some loc) (shape_type shape) | VecReplace (shape, lane) -> check_shape_lanes ctx loc shape lane; let* () = pop_known ctx loc (shape_type shape) in let* () = pop_known ctx loc V128 in push_known (Some loc) V128 | VecShuffle lanes -> if not (String.for_all (fun l -> Char.code l < 32) lanes) then Error.invalid_lane_index ctx.modul.diagnostics ~location:loc 32; let* () = pop_known ctx loc V128 in let* () = pop_known ctx loc V128 in push_known (Some loc) V128 | TableGet idx -> let*! typ, source = get_table ctx idx in let addr_ty = address_type_to_valtype typ.limits.address_type in let* () = pop_known ctx loc addr_ty in push ~source (Some loc) (Ref typ.reftype) | TableSet idx -> let*! typ, source = get_table ctx idx in let addr_ty = address_type_to_valtype typ.limits.address_type in let* () = pop ctx loc ~expected_source:source (Ref typ.reftype) in pop_known ctx loc addr_ty | TableSize idx -> let*! typ, _ = get_table ctx idx in push_known (Some loc) (address_type_to_valtype typ.limits.address_type) | TableGrow idx -> let*! typ, source = get_table ctx idx in let addr_ty = address_type_to_valtype typ.limits.address_type in let* () = pop_known ctx loc addr_ty in let* () = pop ctx loc ~expected_source:source (Ref typ.reftype) in push_known (Some loc) addr_ty | TableFill idx -> let*! typ, source = get_table ctx idx in let addr_ty = address_type_to_valtype typ.limits.address_type in let* () = pop_known ctx loc addr_ty in let* () = pop ctx loc ~expected_source:source (Ref typ.reftype) in pop_known ctx loc addr_ty | TableCopy (idx, idx') -> let*! ty, dst_source = get_table ctx idx in let*! ty', src_source = get_table ctx idx' in if not (Types.val_subtype ctx.modul.subtyping_info (Ref ty'.reftype) (Ref ty.reftype)) then Error.type_mismatch ctx.modul.diagnostics ~location:loc ~provided_source:src_source ~expected_source:dst_source; (* The length operand uses the smaller of the two address types: i32 if either table is 32-bit, i64 only if both are 64-bit. *) let address_type = match (ty.limits.address_type, ty'.limits.address_type) with | `I32, _ | _, `I32 -> `I32 | `I64, `I64 -> `I64 in let addr_ty = address_type_to_valtype ty.limits.address_type in let addr_ty' = address_type_to_valtype ty'.limits.address_type in let addr_ty'' = address_type_to_valtype address_type in let* () = pop_known ctx loc addr_ty'' in let* () = pop_known ctx loc addr_ty' in pop_known ctx loc addr_ty | TableInit (idx, idx') -> let*! tabletype, table_source = get_table ctx idx in let*! typ, elem_source = get_elem ctx idx' in if not (Types.val_subtype ctx.modul.subtyping_info (Ref typ) (Ref tabletype.reftype)) then Error.type_mismatch ctx.modul.diagnostics ~location:loc ~provided_source:elem_source ~expected_source:table_source; let addr_ty = address_type_to_valtype tabletype.limits.address_type in let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc I32 in pop_known ctx loc addr_ty | ElemDrop idx -> let*! _ = get_elem ctx idx in return () | RefNull typ -> let source = Plain Ast.Text.(Ref { nullable = true; typ }) in let*! typ = heaptype ctx.modul.diagnostics ctx.modul.types typ in push ~source (Some loc) (Ref { nullable = true; typ }) | RefFunc idx -> let*! i, type_idx, sign, exact = get_function ctx idx in let param_source, result_source = functype_sources sign in record (Some idx.info) (Signature (param_source, result_source)); if not ((not !validate_refs) || Hashtbl.mem ctx.modul.refs (Sequence.get_index ctx.modul.functions idx)) then Error.ref_func_inaccessible ctx.modul.diagnostics ~location:loc idx; (* Name the function's type when it was declared with a named type, otherwise show the signature inline (a numeric index would be meaningless, as the interned index space is deduplicated). *) let source = match type_idx with | Some ({ desc = Id _; _ } as idx) -> (* Match the pushed internal type's exactness (below) so the source rendering agrees with it — but only when the exactness is expressible ([exact_ref_source] renders exact under custom-descriptors, plain otherwise). *) if exact then exact_ref_source ctx idx else named_ref_source idx | _ -> Inline_ref (Func sign) in push ~source (Some loc) (Ref { nullable = false; typ = (if exact then Exact i else Type i) }) | RefIsNull -> ( let* ty, loc' = pop_any ctx loc in match ty with | Bot | Bot_ref | Val (Ref _, _) -> push_known (Some loc) I32 | Val (_, source) -> Error.expected_ref_type ctx.modul.diagnostics ~location:loc ~src_loc:loc' ~source; unreachable) | RefAsNonNull -> ( let* ty, loc' = pop_any ctx loc in match ty with | Bot | Bot_ref -> push_bot_ref (Some loc) | Val (Ref ty, source) -> push ~source:(non_null_source source) (Some loc) (Ref { ty with nullable = false }) | Val (_, source) -> Error.expected_ref_type ctx.modul.diagnostics ~location:loc ~src_loc:loc' ~source; unreachable) | RefEq -> let* () = pop_known ctx loc (Ref { nullable = true; typ = Eq }) in let* () = pop_known ctx loc (Ref { nullable = true; typ = Eq }) in push_known (Some loc) I32 | RefTest ty -> let*! ty = reftype ctx.modul.diagnostics ctx.modul.types ty in (match top_heap_type ctx ty.typ with | Cont -> Error.invalid_cast_type ctx.modul.diagnostics ~location:loc | _ -> ()); let* () = lint_cast ctx ~location:loc ~is_test:true ty in let* () = pop_known ctx loc (Ref { nullable = true; typ = top_heap_type ctx ty.typ }) in push_known (Some loc) I32 | RefCast ty -> let source = Plain Ast.Text.(Ref ty) in let*! ty = reftype ctx.modul.diagnostics ctx.modul.types ty in (match top_heap_type ctx ty.typ with | Cont -> Error.invalid_cast_type ctx.modul.diagnostics ~location:loc | _ -> ()); let* () = lint_cast ctx ~location:loc ~is_test:false ty in let* () = pop_known ctx loc (Ref { nullable = true; typ = top_heap_type ctx ty.typ }) in push ~source (Some loc) (Ref ty) | RefCastDescEq ty -> let source = Plain Ast.Text.(Ref ty) in let*! ty = reftype ctx.modul.diagnostics ctx.modul.types ty in (* The descriptor operand (top of stack); its exactness matches the target [ty]. *) let*! desc_ht = descriptor_operand_type ctx ~location:loc ty.typ in let* () = pop ctx loc ~expected_source:(descriptor_operand_source ctx.modul.types ty.typ) (Ref { nullable = true; typ = desc_ht }) in let* () = pop_known ctx loc (Ref { nullable = true; typ = top_heap_type ctx ty.typ }) in push ~source (Some loc) (Ref ty) | RefGetDesc idx -> let*! ty, _, _ = lookup_struct_type ctx idx in let*! desc = type_descriptor ctx ~location:i.info ty in let* entry, _ = pop_any ctx loc in (* [exact_1] is shared between the operand type [(ref null (exact_1 idx))] and the result [(ref (exact_1 desc))]: the descriptor is exact exactly when the operand is a subtype of the *exact* operand type. A subtype [(ref (exact $c))] with [$c <: idx] is NOT — its descriptor is [$c]'s, not [idx]'s — so the result is inexact there. A polymorphic operand (unreachable) fits either; take the most precise, exact. Validate the operand is a reference to [idx] (a subtype or null) either way. *) let exact = match entry with | Bot | Bot_ref -> true | Val (ty', source) -> if not (Types.val_subtype ctx.modul.subtyping_info ty' (Ref { nullable = true; typ = Type ty })) then Error.type_mismatch ctx.modul.diagnostics ~location:loc ~provided_source:source ~expected_source:(named_ref_null_source idx); Types.val_subtype ctx.modul.subtyping_info ty' (Ref { nullable = true; typ = Exact ty }) in push ~source: (descriptor_operand_source ~nullable:false ctx.modul.types (if exact then Exact ty else Type ty)) (Some loc) (Ref { nullable = false; typ = (if exact then Exact desc else Type desc) }) | StructNew idx -> let*! ty, _, fields = lookup_struct_type ctx idx in if Option.is_some (Types.get_subtype ctx.modul.subtyping_info ty).descriptor then Error.descriptor_allocation_required ctx.modul.diagnostics ~location:i.info; let* () = pop_args ctx loc ~source: (Array.init (Array.length fields) (source_field_valtype ctx idx)) (Array.map (fun (f : fieldtype) -> match f.typ with Value v -> v | Packed _ -> I32) fields) in push ~source:(exact_ref_source ctx idx) (Some loc) (Ref { nullable = false; typ = Exact ty }) | StructNewDefault idx -> let*! ty, _, fields = lookup_struct_type ctx idx in if not (Array.for_all field_has_default fields) then Error.not_defaultable ctx.modul.diagnostics ~location:i.info; if Option.is_some (Types.get_subtype ctx.modul.subtyping_info ty).descriptor then Error.descriptor_allocation_required ctx.modul.diagnostics ~location:i.info; push ~source:(exact_ref_source ctx idx) (Some loc) (Ref { nullable = false; typ = Exact ty }) | StructNewDesc idx -> let*! ty, _, fields = lookup_struct_type ctx idx in let*! desc = type_descriptor ctx ~location:i.info ty in (* The descriptor operand is on top of the field values. *) let* () = pop ctx loc ~expected_source: (descriptor_operand_source ctx.modul.types (Exact ty)) (Ref { nullable = true; typ = Exact desc }) in let* () = pop_args ctx loc ~source: (Array.init (Array.length fields) (source_field_valtype ctx idx)) (Array.map (fun (f : fieldtype) -> match f.typ with Value v -> v | Packed _ -> I32) fields) in push ~source:(exact_ref_source ctx idx) (Some loc) (Ref { nullable = false; typ = Exact ty }) | StructNewDefaultDesc idx -> let*! ty, _, fields = lookup_struct_type ctx idx in if not (Array.for_all field_has_default fields) then Error.not_defaultable ctx.modul.diagnostics ~location:i.info; let*! desc = type_descriptor ctx ~location:i.info ty in let* () = pop ctx loc ~expected_source: (descriptor_operand_source ctx.modul.types (Exact ty)) (Ref { nullable = true; typ = Exact desc }) in push ~source:(exact_ref_source ctx idx) (Some loc) (Ref { nullable = false; typ = Exact ty }) | StructGet (signage, idx, idx') -> let*! ty, field_map, fields = lookup_struct_type ctx idx in let* () = pop ctx loc ~expected_source:(named_ref_null_source idx) (Ref { nullable = true; typ = Type ty }) in let*! n = struct_field_index ctx idx' field_map fields in (match fields.(n).typ with | Packed _ -> if signage = None then Error.packed_struct_access ctx.modul.diagnostics ~location:i.info | Value _ -> if signage <> None then Error.unpacked_struct_access ctx.modul.diagnostics ~location:i.info); push ~source:(source_field_valtype ctx idx n) (Some loc) (unpack_type fields.(n)) | StructSet (idx, idx') -> let*! ty, field_map, fields = lookup_struct_type ctx idx in let*! n = struct_field_index ctx idx' field_map fields in if not fields.(n).mut then Error.immutable ctx.modul.diagnostics ~location:i.info "field"; let* () = pop ctx loc ~expected_source:(source_field_valtype ctx idx n) (unpack_type fields.(n)) in pop ctx loc ~expected_source:(named_ref_null_source idx) (Ref { nullable = true; typ = Type ty }) | ArrayNew idx -> let*! ty, field = lookup_array_type ctx idx in let* () = pop_known ctx loc I32 in let* () = pop ctx loc ~expected_source:(source_element_valtype ctx idx) (unpack_type field) in push ~source:(exact_ref_source ctx idx) (Some loc) (Ref { nullable = false; typ = Exact ty }) | ArrayNewDefault idx -> let*! ty, field = lookup_array_type ctx idx in if not (field_has_default field) then Error.not_defaultable ctx.modul.diagnostics ~location:i.info; let* () = pop_known ctx loc I32 in push ~source:(exact_ref_source ctx idx) (Some loc) (Ref { nullable = false; typ = Exact ty }) | ArrayNewFixed (idx, n) -> let*! ty, field = lookup_array_type ctx idx in let* () = pop_repeat ctx loc ~expected_source:(source_element_valtype ctx idx) (unpack_type field) (Uint32.to_int n) in push ~source:(exact_ref_source ctx idx) (Some loc) (Ref { nullable = false; typ = Exact ty }) | ArrayNewData (idx, idx') -> let*! ty, field = lookup_array_type ctx idx in ignore (get_data ctx idx'); (match field.typ with | Packed _ | Value (I32 | I64 | F32 | F64 | V128) -> () | Value (Ref _) -> Error.numeric_array_required ctx.modul.diagnostics ~location:i.info); let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc I32 in push ~source:(exact_ref_source ctx idx) (Some loc) (Ref { nullable = false; typ = Exact ty }) | ArrayNewElem (idx, idx') -> let*! ty, field = lookup_array_type ctx idx in let*! ty', _ = get_elem ctx idx' in (match field.typ with | Value ty when Types.val_subtype ctx.modul.subtyping_info (Ref ty') ty -> () | _ -> Error.incompatible_array_element ctx.modul.diagnostics ~location:i.info); let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc I32 in push ~source:(exact_ref_source ctx idx) (Some loc) (Ref { nullable = false; typ = Exact ty }) | ArrayGet (signage, idx) -> let*! ty, field = lookup_array_type ctx idx in (match field.typ with | Packed _ -> if signage = None then Error.packed_array_access ctx.modul.diagnostics ~location:i.info | Value _ -> if signage <> None then Error.unpacked_array_access ctx.modul.diagnostics ~location:i.info); let* () = pop_known ctx loc I32 in let* () = pop ctx loc ~expected_source:(named_ref_null_source idx) (Ref { nullable = true; typ = Type ty }) in push ~source:(source_element_valtype ctx idx) (Some loc) (unpack_type field) | ArraySet idx -> let*! ty, field = lookup_array_type ctx idx in if not field.mut then Error.immutable ctx.modul.diagnostics ~location:i.info "array"; let* () = pop ctx loc ~expected_source:(source_element_valtype ctx idx) (unpack_type field) in let* () = pop_known ctx loc I32 in pop ctx loc ~expected_source:(named_ref_null_source idx) (Ref { nullable = true; typ = Type ty }) | ArrayLen -> let* () = pop_known ctx loc (Ref { nullable = true; typ = Array }) in push_known (Some loc) I32 | ArrayFill idx -> let*! ty, field = lookup_array_type ctx idx in if not field.mut then Error.immutable ctx.modul.diagnostics ~location:i.info "array"; let* () = pop_known ctx loc I32 in let* () = pop ctx loc ~expected_source:(source_element_valtype ctx idx) (unpack_type field) in let* () = pop_known ctx loc I32 in pop ctx loc ~expected_source:(named_ref_null_source idx) (Ref { nullable = true; typ = Type ty }) | ArrayCopy (idx1, idx2) -> let*! ty1, field1 = lookup_array_type ctx idx1 in let*! ty2, field2 = lookup_array_type ctx idx2 in if not field1.mut then Error.immutable ctx.modul.diagnostics ~location:i.info "array"; if not (storage_subtype ctx.modul.subtyping_info field1.typ field2.typ) then Error.incompatible_array_element ctx.modul.diagnostics ~location:i.info; let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc I32 in let* () = pop ctx loc ~expected_source:(named_ref_null_source idx2) (Ref { nullable = true; typ = Type ty2 }) in let* () = pop_known ctx loc I32 in pop ctx loc ~expected_source:(named_ref_null_source idx1) (Ref { nullable = true; typ = Type ty1 }) | ArrayInitData (idx, idx') -> let*! ty, field = lookup_array_type ctx idx in ignore (get_data ctx idx'); if not field.mut then Error.immutable ctx.modul.diagnostics ~location:i.info "array"; (match field.typ with | Packed _ | Value (I32 | I64 | F32 | F64 | V128) -> () | Value (Ref _) -> Error.numeric_array_required ctx.modul.diagnostics ~location:i.info); let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc I32 in pop ctx loc ~expected_source:(named_ref_null_source idx) (Ref { nullable = true; typ = Type ty }) | ArrayInitElem (idx, idx') -> let*! ty, field = lookup_array_type ctx idx in let*! ty', _ = get_elem ctx idx' in if not field.mut then Error.immutable ctx.modul.diagnostics ~location:i.info "array"; (match field.typ with | Value ty when Types.val_subtype ctx.modul.subtyping_info (Ref ty') ty -> () | _ -> Error.incompatible_array_element ctx.modul.diagnostics ~location:i.info); let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc I32 in pop ctx loc ~expected_source:(named_ref_null_source idx) (Ref { nullable = true; typ = Type ty }) | RefI31 -> let* () = pop_known ctx loc I32 in push_known (Some loc) (Ref { nullable = false; typ = I31 }) | I31Get _ -> let* () = pop_known ctx loc (Ref { nullable = true; typ = I31 }) in push_known (Some loc) I32 | Const (I32 _) -> push_known (Some loc) I32 | Const (I64 _) -> push_known (Some loc) I64 | Const (F32 _) -> push_known (Some loc) F32 | Const (F64 _) -> push_known (Some loc) F64 | UnOp (I32 op) -> let expected, returned = int_un_op_type I32 op in let* () = pop_known ctx loc expected in push_known (Some loc) returned | UnOp (I64 op) -> let expected, returned = int_un_op_type I64 op in let* () = pop_known ctx loc expected in push_known (Some loc) returned | UnOp (F32 op) -> let expected = float_un_op_type F32 op in let* () = pop_known ctx loc expected in push_known (Some loc) F32 | UnOp (F64 op) -> let expected = float_un_op_type F64 op in let* () = pop_known ctx loc expected in push_known (Some loc) F64 | BinOp (I32 op) -> let* () = pop_known ctx loc I32 in let* () = pop_known ctx loc I32 in push_known (Some loc) (int_bin_op_type I32 op) | BinOp (I64 op) -> let* () = pop_known ctx loc I64 in let* () = pop_known ctx loc I64 in push_known (Some loc) (int_bin_op_type I64 op) | BinOp (F32 op) -> let* () = pop_known ctx loc F32 in let* () = pop_known ctx loc F32 in push_known (Some loc) (float_bin_op_type F32 op) | BinOp (F64 op) -> let* () = pop_known ctx loc F64 in let* () = pop_known ctx loc F64 in push_known (Some loc) (float_bin_op_type F64 op) | Add128 | Sub128 -> let* () = pop_known ctx loc I64 in let* () = pop_known ctx loc I64 in let* () = pop_known ctx loc I64 in let* () = pop_known ctx loc I64 in let* () = push_known (Some loc) I64 in push_known (Some loc) I64 | MulWide _ -> let* () = pop_known ctx loc I64 in let* () = pop_known ctx loc I64 in let* () = push_known (Some loc) I64 in push_known (Some loc) I64 | I32WrapI64 -> let* () = pop_known ctx loc I64 in push_known (Some loc) I32 | I64ExtendI32 _ -> let* () = pop_known ctx loc I32 in push_known (Some loc) I64 | F32DemoteF64 -> let* () = pop_known ctx loc F64 in push_known (Some loc) F32 | F64PromoteF32 -> let* () = pop_known ctx loc F32 in push_known (Some loc) F64 | ExternConvertAny -> let* tt, _ = pop_any ctx loc in let nullable = convert_operand_nullable ctx loc tt ~typ:Any in push_known (Some loc) (Ref { nullable; typ = Extern }) | AnyConvertExtern -> let* tt, _ = pop_any ctx loc in let nullable = convert_operand_nullable ctx loc tt ~typ:Extern in push_known (Some loc) (Ref { nullable; typ = Any }) | Folded (i, l) -> let* () = instructions ctx l in instruction ctx i | String (Some idx, s) -> let*! ty, field = lookup_array_type ctx idx in (match field.typ with | Packed I8 -> () | Packed I16 -> let s = Wax_utils.Ast.concat_desc s in if not (String.is_valid_utf_8 s) then Error.string_not_unicode ctx.modul.diagnostics ~location:i.info | Value _ -> Error.string_array_required ctx.modul.diagnostics ~location:i.info); push ~source:(exact_ref_source ctx idx) (Some loc) (Ref { nullable = false; typ = Exact ty }) | String (None, _) -> let i = string_type ctx.modul.types in let comptype = Ast.Text.Array { mut = true; typ = Packed I8 } in push ~source:(Inline_ref comptype) (Some loc) (Ref { nullable = false; typ = Exact i }) | Char _ -> push_known (Some loc) I32 (* Conditional annotations are spliced out by [specialize] before a configuration is validated, so none can remain at this point. *) | If_annotation _ -> assert false (* Wraps {!instruction_core} to feed the editor type sink (§ [recorded_types]). Two adjustments, both no-ops when the sink is off: - a folded instruction [(op … operands)] reads as one unit, so the type its head produces is relocated from the operator token to the whole folded span; nested operands keep their own (smaller) folded spans, so hovering an operand still shows its own type; - an instruction that leaves nothing on the stack ([drop], [local.set], [nop], [br], a call to a void function, …) records a void marker at its span, so hover shows nothing there rather than falling through to the enclosing instruction's type. *) and instruction ctx i st = match !recorded_types with | None -> instruction_core ctx i st | Some r -> ( match i.desc with | Hinted _ -> instruction_core ctx i st | Folded (head, _) -> let st', () = instruction_core ctx i st in (* The head is validated last, so its entries are at the front. Pop them off the operator span and re-record them at the folded span, keeping each entry's configuration index. *) let rec take acc = match !r with | (l0, cfg, t) :: tl when l0 = head.Ast.info -> r := tl; take ((cfg, t) :: acc) | _ -> acc in List.iter (fun (cfg, t) -> r := (i.info, cfg, t) :: !r) (take []); (st', ()) | _ -> let before = !r in let st', () = instruction_core ctx i st in let produced = (not (!r == before)) && match !r with (l0, _, _) :: _ -> l0 = i.info | [] -> false in if not produced then r := (i.info, !sink_config, No_result) :: !r; (st', ())) and instructions ctx l = match l with | [] -> return () | i :: r -> let* () = instruction ctx i in instructions ctx r and block ctx loc label ~used ~param_source ~result_source ~br_source ~params ~results ~br_params block = with_empty_stack ctx.modul loc (let* () = push_results ~sink:false ~loc ~source:param_source params in let* () = instructions { ctx with control_types = (Option.map (fun l -> l.Ast.desc) label, br_params, br_source, used) :: ctx.control_types; } block in pop_args ctx loc ~source:result_source (*ZZZ More precise loc*) results) (*** Constant expressions ***) let rec check_constant_instruction ctx (i : _ Ast.Text.instr) = match i.desc with | GlobalGet idx -> let*? ty, _ = Sequence.get ctx.diagnostics ctx.globals idx in if ty.mut then Error.non_constant_global ctx.diagnostics ~location:idx.info idx | RefFunc i -> (* Record the referenced function by INDEX, not by its type: a ref.func in a body is valid only if that SAME function occurs outside any body, so keying by type would wrongly accept any other same-typed function. *) let*? _ = Sequence.get ctx.diagnostics ctx.functions i in Hashtbl.replace ctx.refs (Sequence.get_index ctx.functions i) () | RefNull _ | StructNew _ | StructNewDefault _ | StructNewDesc _ | StructNewDefaultDesc _ | ArrayNew _ | ArrayNewDefault _ | ArrayNewFixed _ (* [cont.new] allocates a fresh continuation from a (constant) function reference, so it is itself a constant expression. The stack-switching spec and reference tools do not list it yet; this tracks the open spec PR. *) | ContNew _ | RefI31 | Const _ | BinOp (I32 (Add | Sub | Mul) | I64 (Add | Sub | Mul)) | ExternConvertAny | AnyConvertExtern | VecConst _ | String _ | Char _ -> () | Folded (i, l) -> check_constant_instruction ctx i; check_constant_instructions ctx l | Block _ | Loop _ | If _ | TryTable _ | Try _ | Unreachable | Nop | Throw _ | ThrowRef | ContBind _ | Suspend _ | Resume _ | ResumeThrow _ | ResumeThrowRef _ | Switch _ | Br _ | Br_if _ | Br_table _ | Br_on_null _ | Br_on_non_null _ | Br_on_cast _ | Br_on_cast_fail _ | Br_on_cast_desc_eq _ | Br_on_cast_desc_eq_fail _ | Hinted _ | Return | Call _ | CallRef _ | CallIndirect _ | ReturnCall _ | ReturnCallRef _ | ReturnCallIndirect _ | Drop | Select _ | LocalGet _ | LocalSet _ | LocalTee _ | GlobalSet _ | Load _ | LoadS _ | Store _ | StoreS _ | Atomic _ | AtomicFence | MemorySize _ | MemoryGrow _ | MemoryFill _ | MemoryCopy _ | MemoryInit _ | DataDrop _ | TableGet _ | TableSet _ | TableSize _ | TableGrow _ | TableFill _ | TableCopy _ | TableInit _ | ElemDrop _ | RefIsNull | RefAsNonNull | RefEq | RefTest _ | RefCast _ | RefCastDescEq _ | RefGetDesc _ | StructGet _ | StructSet _ | ArrayNewData _ | ArrayNewElem _ | ArrayGet _ | ArraySet _ | ArrayLen | ArrayFill _ | ArrayCopy _ | ArrayInitData _ | ArrayInitElem _ | I31Get _ | UnOp _ | Add128 | Sub128 | MulWide _ | BinOp ( F32 _ | F64 _ | I32 ( Div _ | Rem _ | And | Or | Xor | Shl | Shr _ | Rotl | Rotr | Eq | Ne | Lt _ | Gt _ | Le _ | Ge _ ) | I64 ( Div _ | Rem _ | And | Or | Xor | Shl | Shr _ | Rotl | Rotr | Eq | Ne | Lt _ | Gt _ | Le _ | Ge _ ) ) | I32WrapI64 | I64ExtendI32 _ | F32DemoteF64 | F64PromoteF32 | VecBitselect | VecUnOp _ | VecBinOp _ | VecTest _ | VecShift _ | VecBitmask _ | VecLoad _ | VecStore _ | VecLoadLane _ | VecStoreLane _ | VecLoadSplat _ | VecExtract _ | VecReplace _ | VecSplat _ | VecShuffle _ | VecTernOp _ -> Error.constant_expression_required ctx.diagnostics ~location:i.info (* Spliced out by [specialize] before validation; cannot occur here. *) | If_annotation _ -> assert false and check_constant_instructions ctx l = List.iter (fun i -> check_constant_instruction ctx i) l let constant_expression ctx ~location ~expected_source ty expr = check_constant_instructions ctx expr; with_empty_stack ctx location (let ctx = { locals = Sequence.make "local"; control_types = []; return_types = [||]; return_source = [||]; modul = ctx; initialized_locals = IntSet.empty; used_locals = ref IntSet.empty; label_decls = ref []; } in let* () = instructions ctx expr in pop ctx location ~expected_source ty) (*** Type registration and the module environment ***) let add_type d ctx ty = Array.iteri (fun i e -> let label, (sub : Ast.Text.subtype) = e.Ast.desc in (* These forward references are placeholders during the rec group's own resolution and are replaced (or dropped) below; the composite type is not consulted meanwhile, but carrying it keeps the field total. *) Hashtbl.replace ctx.index_mapping (Uint32.of_int (ctx.last_index + i)) (Types.Rec i, [], sub.typ, Some e); Option.iter (fun label -> Hashtbl.replace ctx.label_mapping label.Ast.desc (Types.Rec i, [], sub.typ, Some e)) label) ty; match rectype d ctx ty with | None -> Array.iteri (fun i e -> let label = fst e.Ast.desc in Hashtbl.remove ctx.index_mapping (Uint32.of_int (ctx.last_index + i)); Option.iter (fun label -> Hashtbl.remove ctx.label_mapping label.Ast.desc) label) ty | Some ty' -> (* Well-formedness of [descriptor] / [describes] clauses, which must link two struct types within the same recursion group. In [ty'] a [Rec] reference names a member of this group; a [Def] denotes an already-defined type outside it. *) Array.iteri (fun i (sub : Types.Normalized.subtype) -> let location = ty.(i).Ast.info in (match sub.descriptor with | None -> () | Some (Def _) -> Error.descriptor_outside_rec_group d ~location ~described:false | Some (Rec pos) -> ( (* This type is described by [ty'.(pos)]; that descriptor must describe this type back. *) match ty'.(pos).describes with | Some (Rec o) when o = i -> () | _ -> Error.descriptor_not_reciprocal d ~location ~described:false)); (match sub.describes with | None -> () | Some (Def _) -> Error.descriptor_outside_rec_group d ~location ~described:true | Some (Rec pos) -> ( if pos >= i then Error.forward_use_of_described d ~location; (* This type is the descriptor of [ty'.(pos)], which must name this type as its descriptor. *) match ty'.(pos).descriptor with | Some (Rec dd) when dd = i -> () | _ -> Error.descriptor_not_reciprocal d ~location ~described:true )); if (sub.descriptor <> None || sub.describes <> None) && match sub.typ with Struct _ -> false | _ -> true then Error.descriptor_not_struct d ~location ~described:(sub.describes <> None)) ty'; let i' = Types.add_rectype ctx.types ty' in Array.iteri (fun i e -> let label, typ = e.Ast.desc in let fields = match (typ : Ast.Text.subtype).typ with | Struct fields -> Array.mapi (fun i e -> match fst e.Ast.desc with | Some id -> Some (id.Ast.desc, i) | None -> None) fields |> Array.to_list |> List.filter_map Fun.id | _ -> [] in Hashtbl.replace ctx.index_mapping (Uint32.of_int (ctx.last_index + i)) (Types.Def (Types.Id.add i' i), fields, typ.typ, Some e); let def_idx = let desc = match label with | Some l -> Ast.Text.Id l.Ast.desc | None -> Ast.Text.Num (Uint32.of_int (ctx.last_index + i)) in { Ast.desc; info = e.Ast.info } in let cont_ref = match (typ : Ast.Text.subtype).typ with | Cont r -> Some r | Func _ | Struct _ | Array _ -> None in Hashtbl.replace ctx.type_defs (ctx.last_index + i) (def_idx, cont_ref); Option.iter (fun node -> Hashtbl.replace ctx.descriptor_source (Types.Id.add i' i) node) (typ : Ast.Text.subtype).descriptor; Option.iter (fun label -> Hashtbl.replace ctx.label_mapping label.Ast.desc (Types.Def (Types.Id.add i' i), fields, typ.typ, Some e)) label) ty; ctx.last_index <- ctx.last_index + Array.length ty let register_exports ctx lst = List.iter (fun (name : Ast.Text.name) -> if Hashtbl.mem ctx.exports name.desc then Error.duplicated_export ctx.diagnostics ~location:name.info name else Hashtbl.add ctx.exports name.desc ()) lst let limits ctx kind { Ast.desc = { mi; ma; address_type; page_size_log2; }; info = location; } max_fn = (match page_size_log2 with | None | Some (0 | 16) -> () | Some _ -> Error.invalid_page_size ctx.diagnostics ~location); (* A shared memory must declare a maximum size. *) if shared && ma = None then Error.shared_memory_without_max ctx.diagnostics ~location; let max = max_fn address_type page_size_log2 in match ma with | None -> if Uint64.compare mi max > 0 then Error.limit_too_large ctx.diagnostics ~location kind max | Some ma -> if Uint64.compare mi ma > 0 then Error.limit_mismatch ctx.diagnostics ~location kind; if Uint64.compare ma max > 0 then Error.limit_too_large ctx.diagnostics ~location kind max (* The maximum number of pages: [min(2^bits - 1, 2^(bits - p))] where [bits] is 32 (i32) or 64 (i64) and [2^p] is the page size (default 2^16). The byte span gives the [2^(bits - p)] term; the [2^bits - 1] cap bounds the page index itself (so e.g. a page size of 1 allows 2^32 - 1 pages, not 2^32). With the default page size this is the familiar 65536 / 2^48 pages. *) let max_memory_size address_type page_size_log2 = let p = match page_size_log2 with None -> 16 | Some p -> p in let bits, index_max = match address_type with | `I32 -> (32, Uint64.of_string "0xffff_ffff") | `I64 -> (64, Uint64.of_string "0xffff_ffff_ffff_ffff") in let e = bits - p in let by_page = if e >= 64 then index_max else if e <= 0 then Uint64.zero else Uint64.of_int64 (Int64.shift_left 1L e) in if Uint64.compare index_max by_page <= 0 then index_max else by_page let max_table_size address_type _page_size_log2 = match address_type with | `I32 -> Uint64.of_string "0xffff_ffff" | `I64 -> Uint64.of_string "0xffff_ffff_ffff_ffff" let rec register_typeuses d ctx l = List.iter (fun i -> register_typeuses_instr d ctx i) l and register_typeuses_instr d ctx (i : _ Ast.Text.instr) = match i.desc with | Block { typ; _ } | Loop { typ; _ } | If { typ; _ } | TryTable { typ; _ } | Try { typ; _ } -> ( match typ with | Some (Typeuse use) -> ignore (typeuse d ctx use) | Some (Valtype _) | None -> ()) | CallIndirect (_, use) | ReturnCallIndirect (_, use) -> ignore (typeuse d ctx use) | String _ -> ignore (string_type ctx) | If_annotation _ -> (* Spliced out by [specialize] before validation; cannot occur here. *) assert false | Folded (i, l) -> register_typeuses_instr d ctx i; register_typeuses d ctx l | Hinted (_, i) -> register_typeuses_instr d ctx i | Unreachable | Nop | Throw _ | ThrowRef | ContNew _ | ContBind _ | Suspend _ | Resume _ | ResumeThrow _ | ResumeThrowRef _ | Switch _ | Br _ | Br_if _ | Br_table _ | Br_on_null _ | Br_on_non_null _ | Br_on_cast _ | Br_on_cast_fail _ | Br_on_cast_desc_eq _ | Br_on_cast_desc_eq_fail _ | Return | Call _ | CallRef _ | ReturnCall _ | ReturnCallRef _ | Drop | Select _ | LocalGet _ | LocalSet _ | LocalTee _ | GlobalGet _ | GlobalSet _ | Load _ | LoadS _ | Store _ | StoreS _ | Atomic _ | AtomicFence | MemorySize _ | MemoryGrow _ | MemoryFill _ | MemoryCopy _ | MemoryInit _ | DataDrop _ | TableGet _ | TableSet _ | TableSize _ | TableGrow _ | TableFill _ | TableCopy _ | TableInit _ | ElemDrop _ | RefNull _ | RefFunc _ | RefIsNull | RefAsNonNull | RefEq | RefTest _ | RefCast _ | RefCastDescEq _ | RefGetDesc _ | StructNew _ | StructNewDefault _ | StructNewDesc _ | StructNewDefaultDesc _ | StructGet _ | StructSet _ | ArrayNew _ | ArrayNewDefault _ | ArrayNewFixed _ | ArrayNewData _ | ArrayNewElem _ | ArrayGet _ | ArraySet _ | ArrayLen | ArrayFill _ | ArrayCopy _ | ArrayInitData _ | ArrayInitElem _ | RefI31 | I31Get _ | Const _ | UnOp _ | BinOp _ | Add128 | Sub128 | MulWide _ | I32WrapI64 | I64ExtendI32 _ | F32DemoteF64 | F64PromoteF32 | ExternConvertAny | AnyConvertExtern | VecBitselect | VecConst _ | VecUnOp _ | VecBinOp _ | VecTest _ | VecShift _ | VecBitmask _ | VecLoad _ | VecStore _ | VecLoadLane _ | VecStoreLane _ | VecLoadSplat _ | VecExtract _ | VecReplace _ | VecSplat _ | VecShuffle _ | VecTernOp _ | Char _ -> () (* Collect the implicit function types denoted by inline signatures (function and tag definitions, imports, block types and [call_indirect]). Following the text format, such a type reuses a structurally-equal type if one already exists, and is otherwise appended to the end of the type index space, where it can be referred to by index. We must do this before resolving any type reference so that those indices are bound, and before computing the subtyping information so that it covers every type. Relies on {!Types.add_rectype} deduplicating: a [typeuse] encountered later during validation then resolves to the type collected here instead of growing the type table. *) let collect_implicit_types d ctx fields = let collect sign = let>@ ft = n_functype d ctx sign in let before = Types.last_index ctx.types in let idx = Types.add_rectype ctx.types [| { typ = Func ft; supertype = None; final = true; descriptor = None; describes = None; }; |] in if Types.last_index ctx.types > before then ( Hashtbl.replace ctx.index_mapping (Uint32.of_int ctx.last_index) (Types.Def idx, [], Func sign, None); ctx.last_index <- ctx.last_index + 1) in let collect_instr (i : _ Ast.Text.instr) = match i.desc with | Block { typ = Some (Typeuse (None, Some ft)); _ } | Loop { typ = Some (Typeuse (None, Some ft)); _ } | If { typ = Some (Typeuse (None, Some ft)); _ } | Try { typ = Some (Typeuse (None, Some ft)); _ } | TryTable { typ = Some (Typeuse (None, Some ft)); _ } -> collect ft | CallIndirect (_, (None, Some ft)) | ReturnCallIndirect (_, (None, Some ft)) -> collect ft | If_annotation _ -> (* Spliced out by [specialize] before validation; cannot occur here. *) assert false | _ -> () in (* The canonical walk descends into every nesting instruction, branch hints included, so inline types buried there are interned like any other. *) let collect_instrs l = List.iter (Ast_utils.iter_instr collect_instr) l in List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> (match field.desc with | Import { desc = Func { typ = None, Some sign; _ }; _ } | Import { desc = Tag (None, Some sign); _ } | Func { typ = None, Some sign; _ } | Tag { typ = None, Some sign; _ } -> collect sign | _ -> ()); match field.desc with | Func { instrs; _ } -> collect_instrs instrs | _ -> ()) (List.concat_map Ast_utils.expand_import_group fields) let build_initial_env ctx fields = List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Import { id; desc; exports; module_ = _; name = _ } -> ( register_exports ctx exports; (* Record a func/global import as an [unused-import] candidate; an inline export re-exports it, so mark it used. *) let location = match id with Some id -> id.Ast.info | None -> field.info in match desc with | Func { exact; typ = tu } -> let idx = Sequence.next_index ctx.functions in ignore (let+@ ty = typeuse ctx.diagnostics ctx.types tu in Sequence.register ctx.functions id (ty, fst tu, typeuse_functype ctx.types tu, exact)); ctx.imported_functions <- (idx, id, location) :: ctx.imported_functions; if exports <> [] then Hashtbl.replace ctx.used_functions idx () | Memory lim -> limits ctx "memory" lim max_memory_size; Sequence.register ctx.memories id lim.desc | Table typ -> limits ctx "table" typ.limits max_table_size; let src = Plain (Ast.Text.Ref typ.reftype) in let>@ typ = tabletype ctx.diagnostics ctx.types typ in Sequence.register ctx.tables id (typ, src) | Global ty -> let idx = Sequence.next_index ctx.globals in let src = Plain ty.typ in let>@ ty = globaltype ctx.diagnostics ctx.types ty in Sequence.register ctx.globals id (ty, src); ctx.imported_globals <- (idx, id, location) :: ctx.imported_globals; if exports <> [] then Hashtbl.replace ctx.used_globals idx () | Tag tu -> let>@ ty = typeuse ctx.diagnostics ctx.types tu in let sign = typeuse_functype ctx.types tu in (* A tag's function type is deliberately not required to have empty results: the stack-switching proposal uses tags with result types (for [suspend] / [resume]), so the exception-handling restriction to no results is not enforced. *) Sequence.register ctx.tags id (ty, sign)) | Func { id; typ; instrs; exports; locals = _ } -> let>@ ty = typeuse ctx.diagnostics ctx.types typ in let sign = typeuse_functype ctx.types typ in (* A module-defined function has exactly its declared type. *) let idx = Sequence.next_index ctx.functions in Sequence.register ctx.functions id (ty, fst typ, sign, true); (* Record it as an [unused-field] candidate; an inline export makes it externally reachable, so mark it used. *) let location = match id with Some id -> id.Ast.info | None -> field.info in ctx.defined_functions <- (idx, id, location) :: ctx.defined_functions; if exports <> [] then Hashtbl.replace ctx.used_functions idx (); register_typeuses ctx.diagnostics ctx.types instrs | Tag { id; typ; exports } -> let>@ ty = typeuse ctx.diagnostics ctx.types typ in let sign = typeuse_functype ctx.types typ in (* A tag's function type is deliberately not required to have empty results: the stack-switching proposal uses tags with result types (for [suspend] / [resume]), so the exception-handling restriction to no results is not enforced. *) register_exports ctx exports; Sequence.register ctx.tags id (ty, sign) | _ -> ()) (List.concat_map Ast_utils.expand_import_group fields) let check_type_definitions ctx = for i = 0 to ctx.types.last_index - 1 do let def_idx, cont_ref = Option.value ~default:(Ast.no_loc (Ast.Text.Num (Uint32.of_int i)), None) (Hashtbl.find_opt ctx.types.type_defs i) in let location = def_idx.Ast.info in let>@ gidx, _, _, _ = get_type_info ctx.diagnostics ctx.types (Ast.no_loc (Ast.Text.Num (Uint32.of_int i))) in let ty = Types.get_subtype ctx.subtyping_info (def_id gidx) in (* A continuation type must wrap a function type. *) (match ty.typ with | Cont ft -> ( match (Types.get_subtype ctx.subtyping_info ft).typ with | Func _ -> () | Struct _ | Array _ | Cont _ -> (* Name the wrapped type as the source wrote it: the resolved index [ft] is canonical, so identical types would otherwise be indistinguishable. A [Cont] type is only ever registered by [add_type], which records its wrapped-type source, so [cont_ref] is necessarily [Some] here. *) let wrapped = match cont_ref with Some r -> r | None -> assert false in Error.expected_func_type ctx.diagnostics ~location wrapped) | Func _ | Struct _ | Array _ -> ()); let*? j = ty.supertype in let ty' = Types.get_subtype ctx.subtyping_info j in let invalid () = Error.invalid_subtype ctx.diagnostics ~location in if ty'.final then invalid () else begin (match (ty.typ, ty'.typ) with | Func { params; results }, Func { params = params'; results = results' } -> if Array.length params <> Array.length params' || Array.length results <> Array.length results' || not (Array.for_all2 (fun p p' -> Types.val_subtype ctx.subtyping_info p' p) params params' && Array.for_all2 (fun r r' -> Types.val_subtype ctx.subtyping_info r r') results results') then invalid () | Struct fields, Struct fields' -> if Array.length fields' > Array.length fields || not (Array.for_all2 (field_subtype ctx.subtyping_info) (Array.sub fields 0 (Array.length fields')) fields') then invalid () | Array field, Array field' -> if not (field_subtype ctx.subtyping_info field field') then invalid () | Cont ft, Cont ft' -> if not (Types.heap_subtype ctx.subtyping_info (Type ft) (Type ft')) then invalid () | Func _, (Struct _ | Array _ | Cont _) | Struct _, (Func _ | Array _ | Cont _) | Array _, (Func _ | Struct _ | Cont _) | Cont _, (Func _ | Struct _ | Array _) -> Error.supertype_mismatch ctx.diagnostics ~location); (* If the supertype has a descriptor, the subtype must too, and its descriptor must be a subtype of the supertype's. (A subtype may add a descriptor that its supertype lacks.) *) (match ty'.descriptor with | None -> () | Some dp -> ( match ty.descriptor with | Some ds when Types.heap_subtype ctx.subtyping_info (Type ds) (Type dp) -> () | _ -> invalid ())); (* A subtype has a described type iff its supertype does, and the subtype's described type must be a subtype of the supertype's. *) match (ty.describes, ty'.describes) with | None, None -> () | Some os, Some op -> if not (Types.heap_subtype ctx.subtyping_info (Type os) (Type op)) then invalid () | Some _, None | None, Some _ -> invalid () end done (*** Module-field validation passes ***) let tables_and_memories ctx fields = List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Memory { id; limits = lim; init = _; exports } -> limits ctx "memory" lim max_memory_size; Sequence.register ctx.memories id lim.desc; register_exports ctx exports | Table { id; typ; init; exports } -> limits ctx "table" typ.limits max_table_size; let src = Plain (Ast.Text.Ref typ.reftype) in let>@ typ = tabletype ctx.diagnostics ctx.types typ in (match init with | Init_default -> if not typ.reftype.nullable then Error.non_nullable_table_type ctx.diagnostics ~location:field.info (*ZZZ*) | Init_expr e -> constant_expression ctx ~location:field.info ~expected_source:src (Ref typ.reftype) e | Init_segment _ -> ()); Sequence.register ctx.tables id (typ, src); register_exports ctx exports | _ -> ()) fields let globals ctx fields = List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Global { id; typ; init; exports } -> let src = Plain typ.typ in let>@ typ = globaltype ctx.diagnostics ctx.types typ in constant_expression ctx ~location:field.info ~expected_source:src typ.typ init; let idx = Sequence.next_index ctx.globals in Sequence.register ctx.globals id (typ, src); (* Record it as an [unused-field] candidate; an inline export makes it externally reachable, so mark it used. *) let location = match id with Some id -> id.Ast.info | None -> field.info in ctx.defined_globals <- (idx, id, location) :: ctx.defined_globals; if exports <> [] then Hashtbl.replace ctx.used_globals idx (); register_exports ctx exports | String_global { id; typ; init } -> (* A named array type is honoured (and must be an i8/i16 array, like any string); with none, the global takes the default [<string>] ([mut i8]) type. *) let ty, src = match typ with | None -> ( string_type ctx.types, Inline_ref (Ast.Text.Array { mut = true; typ = Packed I8 }) ) | Some idx -> ( match resolve_type_index ctx.diagnostics ctx.types idx with | None -> ( string_type ctx.types, Inline_ref (Ast.Text.Array { mut = true; typ = Packed I8 }) ) | Some ty -> (match (Types.get_subtype ctx.subtyping_info ty).typ with | Array { typ = Packed I8; _ } -> () | Array { typ = Packed I16; _ } -> let s = Wax_utils.Ast.concat_desc init in if not (String.is_valid_utf_8 s) then Error.string_not_unicode ctx.diagnostics ~location:idx.info | Array { typ = Value _; _ } -> Error.string_array_required ctx.diagnostics ~location:idx.info | _ -> Error.expected_array_type ctx.diagnostics ~location:idx.info idx); (ty, named_ref_source idx)) in let typ = { mut = false; typ = Ref { nullable = false; typ = Type ty } } in Sequence.register ctx.globals (Some id) (typ, src) | _ -> ()) fields let segments ctx fields = List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Memory { init; _ } -> let*? _ = init in Sequence.register ctx.data None () | Data { id; init = _; mode } -> (match mode with | Passive -> () | Active (i, e) -> let*? limits = Sequence.get ctx.diagnostics ctx.memories i in let aty = address_type_to_valtype limits.address_type in constant_expression ctx ~location:field.info ~expected_source:(source_of_valtype aty) aty e); Sequence.register ctx.data id () | Table { typ; init; _ } -> ( match init with | Init_default | Init_expr _ -> () | Init_segment lst -> let src = Plain (Ast.Text.Ref typ.reftype) in let>@ typ = reftype ctx.diagnostics ctx.types typ.reftype in List.iter (fun e -> constant_expression ctx ~location:field.info ~expected_source:src (Ref typ) e) lst; Sequence.register ctx.elem None (typ, src)) | Elem { id; typ; init; mode } -> let elem_source = Plain (Ast.Text.Ref typ) in let>@ typ = reftype ctx.diagnostics ctx.types typ in (match mode with | Passive | Declare -> () | Active (i, e) -> let*? tabletype, table_source = Sequence.get ctx.diagnostics ctx.tables i in if not (Types.val_subtype ctx.subtyping_info (Ref typ) (Ref tabletype.reftype)) then Error.elem_segment_type_mismatch ctx.diagnostics ~location:field.info ~elem_source ~table_source; let aty = address_type_to_valtype tabletype.limits.address_type in constant_expression ctx ~location:field.info ~expected_source:(source_of_valtype aty) aty e); List.iter (fun e -> constant_expression ctx ~location:field.info ~expected_source:elem_source (Ref typ) e) init; Sequence.register ctx.elem id (typ, elem_source) | _ -> ()) fields (* An exported function is referenceable by [ref.func] (it is in the module's [refs] set), like a function named in a global or element segment. Record exported functions by index BEFORE bodies are validated — the dedicated [exports] pass runs after [functions], too late for the [ref.func] check. Both a standalone export field and an inline export on a function count; in a binary all exports are standalone (the export section), inline being WAT sugar. Function indices are counted positionally, imports first (their order is enforced by [check_import_order]), matching how [build_initial_env] registers them. *) let declared_func_exports ctx fields = let fi = ref 0 in List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Import { desc = Func _; exports; _ } -> if exports <> [] then Hashtbl.replace ctx.refs !fi (); incr fi | Import _ -> () | Func { exports; _ } -> if exports <> [] then Hashtbl.replace ctx.refs !fi (); incr fi | Export { kind = Func; index; _ } -> Option.iter (fun i -> Hashtbl.replace ctx.refs i ()) (Sequence.get_index_opt ctx.functions index) | _ -> ()) (List.concat_map Ast_utils.expand_import_group fields) (*** Correctness lints over a function body (see {!Wax_utils.Warning}) ***) (* A constant operand tracked while linting. Crossing any non-constant instruction clears the stack, so its entries mirror the most recent run of constants on the real operand stack — the top is a binary operator's right operand (its last-pushed value). Folded operands flatten into the same push sequence, so folded and flat forms are handled alike. *) (* A value tracked on the lint stack: a known integer/float constant, a bare [local.get]/[global.get] read (tracked by resolved index so two reads of the same variable can be recognised as identical operands), or some other value produced with no side effect and no trap ([LPure]). Constants and reads are also pure. Anything else clears the stack. *) type lint_val = | LInt of int64 | LFloat of float | LLocal of int | LGlobal of int | LPure let lint_int_value s = Int64.of_string_opt (String.concat "" (String.split_on_char '_' s)) let lint_float_value s = let s = String.concat "" (String.split_on_char '_' s) in let body = if String.length s > 0 && (s.[0] = '+' || s.[0] = '-') then String.sub s 1 (String.length s - 1) else s in if String.length body >= 3 && String.equal (String.sub body 0 3) "nan" then Some Float.nan else float_of_string_opt s (* Whether a trapping (toward-zero) float-to-integer conversion of [f] to the given target/signage would trap: NaN/infinite, or out of range. *) let float_conversion_traps target signage f = if not (Float.is_finite f) then true else let t = Float.trunc f in let pow2 n = Float.ldexp 1. n in match (target, signage) with | `I32, Ast.Signed -> t < -.pow2 31 || t >= pow2 31 | `I32, Ast.Unsigned -> t < 0. || t >= pow2 32 | `I64, Ast.Signed -> t < -.pow2 63 || t >= pow2 63 | `I64, Ast.Unsigned -> t < 0. || t >= pow2 64 (* Report the constant-operand lints (shift count, division/remainder by zero, out-of-range trapping conversion, tautological unsigned comparison, constant condition, discarded constant) and dead code over a function body. *) let lint_body ctx instrs = let diagnostics = ctx.modul.diagnostics in (* The number of field operands of a [struct.new] on the type at [idx], or [None] if the index does not resolve to a struct type. Looks the type up silently (the body has already been validated, so a bad index has already been reported — re-reporting here would duplicate the diagnostic). *) let struct_arity idx = let m = ctx.modul in match try match idx.Ast.desc with | Ast.Text.Num x -> Some (Hashtbl.find m.types.index_mapping x) | Ast.Text.Id id -> Some (Hashtbl.find m.types.label_mapping id) with Not_found -> None with | Some (gidx, _, _, _) -> ( match (Types.get_subtype m.subtyping_info (def_id gidx)).typ with | Struct fields -> Some (Array.length fields) | Func _ | Array _ | Cont _ -> None) | None -> None in (* Two operands that are the same bare local/global read (with nothing impure in between — an assignment would have cleared the stack). *) let same_read a b = match (a, b) with | LLocal i, LLocal j | LGlobal i, LGlobal j -> i = j | _ -> false in let check_int_binop (op : _ Ast.Text.instr) (o : Ast.int_bin_op) width st = let taut value = Error.tautological_comparison diagnostics ~location:op.info ~value in let no_effect () = Error.redundant_operation diagnostics ~location:op.info (Wax_utils.Message.text "This operation has no effect on its result.") in let always v = Error.redundant_operation diagnostics ~location:op.info Wax_utils.Message.( (text "This operation always yields" ++ int64 v) ^^ text ".") in (* [st] is [right :: left :: _]. The redundant-operation cases require both operands to be tracked (so the whole expression is effect-free): a constant on one side plus a second entry ([_ :: _]) for the other. *) match (o, st) with | (Shl | Shr _), LInt n :: _ when n >= 0L && n >= Int64.of_int width -> Error.shift_overflow diagnostics ~location:op.info ~width n | (Div _ | Rem _), LInt 0L :: _ -> Error.division_by_zero diagnostics ~location:op.info (* An unsigned comparison against a constant zero, on either side. *) | Lt Ast.Unsigned, LInt 0L :: _ -> taut false (* a <u 0 *) | Ge Ast.Unsigned, LInt 0L :: _ -> taut true (* a >=u 0 *) | Gt Ast.Unsigned, _ :: LInt 0L :: _ -> taut false (* 0 >u a *) | Le Ast.Unsigned, _ :: LInt 0L :: _ -> taut true (* 0 <=u a *) (* Two identical integer operands: [a == a]/[a <= a]/[a >= a] hold, the strict and inequality forms do not. All comparisons here are integer (the float ones are a different opcode), so there is no NaN caveat. *) | (Eq | Le _ | Ge _), a :: b :: _ when same_read a b -> taut true | (Ne | Lt _ | Gt _), a :: b :: _ when same_read a b -> taut false (* Arithmetic identities: the result is the other operand unchanged. *) | Add, (LInt 0L :: _ :: _ | _ :: LInt 0L :: _) -> no_effect () (* x + 0 *) | (Sub | Shl | Shr _ | Rotl | Rotr), LInt 0L :: _ :: _ -> no_effect () (* x - 0, x << 0, … *) | Mul, (LInt 1L :: _ :: _ | _ :: LInt 1L :: _) -> no_effect () (* x * 1 *) | Div _, LInt 1L :: _ :: _ -> no_effect () (* x / 1 *) | (Or | Xor), (LInt 0L :: _ :: _ | _ :: LInt 0L :: _) -> no_effect () (* x | 0, x ^ 0 *) | (And | Or), a :: b :: _ when same_read a b -> no_effect () (* x & x, x | x *) (* Absorbing operands: the result is a constant, independent of the other. *) | Mul, (LInt 0L :: _ :: _ | _ :: LInt 0L :: _) -> always 0L (* x * 0 *) | And, (LInt 0L :: _ :: _ | _ :: LInt 0L :: _) -> always 0L (* x & 0 *) | Rem _, LInt 1L :: _ :: _ -> always 0L (* x % 1 *) | (Sub | Xor), a :: b :: _ when same_read a b -> always 0L (* x - x, x ^ x *) | _ -> () in (* Check the operator [op] against the constant stack [st] (top = right operand / condition). *) let check_op (op : _ Ast.Text.instr) st = match op.desc with | BinOp (I32 o) -> check_int_binop op o 32 st | BinOp (I64 o) -> check_int_binop op o 64 st | UnOp (I32 (Trunc (_, sign))) -> ( match st with | LFloat f :: _ when float_conversion_traps `I32 sign f -> Error.conversion_out_of_range diagnostics ~location:op.info | _ -> ()) | UnOp (I64 (Trunc (_, sign))) -> ( match st with | LFloat f :: _ when float_conversion_traps `I64 sign f -> Error.conversion_out_of_range diagnostics ~location:op.info | _ -> ()) | Br_if _ | If _ | Select _ -> ( match st with | LInt n :: _ -> Error.constant_condition diagnostics ~location:op.info ~value:(n <> 0L) | _ -> ()) | Drop -> ( match st with | (LInt _ | LFloat _ | LLocal _ | LGlobal _ | LPure) :: _ -> Error.unused_result diagnostics ~location:op.info | _ -> ()) (* A self-assignment [x = x]: the value written is a fresh read of the same variable, with nothing impure in between (which would have cleared it). *) | LocalSet idx -> ( match (st, Sequence.get_index_opt ctx.locals idx) with | LLocal j :: _, Some i when i = j -> Error.redundant_operation diagnostics ~location:op.info (Wax_utils.Message.text "This assignment writes the local back to itself.") | _ -> ()) | GlobalSet idx -> ( match (st, Sequence.get_index_opt ctx.modul.globals idx) with | LGlobal j :: _, Some i when i = j -> Error.redundant_operation diagnostics ~location:op.info (Wax_utils.Message.text "This assignment writes the global back to itself.") | _ -> ()) | _ -> () in (* An instruction after which control does not fall through. *) let rec is_diverging (i : _ Ast.Text.instr) = match i.desc with | Br _ | Br_table _ | Return | Unreachable | ReturnCall _ | ReturnCallRef _ | ReturnCallIndirect _ | Throw _ | ThrowRef -> true | Folded (op, _) -> is_diverging op | _ -> false in (* How an instruction affects the lint's purity tracking. The match is exhaustive so a newly added instruction must be classified rather than silently defaulting. *) let classify (d : _ Ast.Text.instr_desc) = match d with (* Effect-free, non-trapping operators. Every value on the lint stack is pure by construction (impure/unhandled producers clear it), so the results are pure exactly when the stack is deep enough for the operands — [Pure (consumed, produced)] pops [consumed] operands and pushes [produced] pure values. *) | Const _ | LocalGet _ | GlobalGet _ | RefNull _ | RefFunc _ | MemorySize _ | TableSize _ | VecConst _ | StructNewDefault _ -> `Pure (0, 1) | UnOp (I32 (Trunc _) | I64 (Trunc _)) -> `Impure (* trapping float→int conversion *) (* [struct.new_default] with a descriptor takes just the descriptor operand, so its arity is fixed at 1 like the other unary pure ops. *) | UnOp _ | I32WrapI64 | I64ExtendI32 _ | F32DemoteF64 | F64PromoteF32 | ExternConvertAny | AnyConvertExtern | RefIsNull | RefTest _ | RefI31 | VecUnOp _ | VecTest _ | VecBitmask _ | VecExtract _ | VecSplat _ | ArrayNewDefault _ | StructNewDefaultDesc _ -> `Pure (1, 1) | BinOp (I32 (Div _ | Rem _) | I64 (Div _ | Rem _)) -> `Impure (* integer division/remainder may trap *) | BinOp _ | RefEq | VecBinOp _ | VecShift _ | VecReplace _ | VecShuffle _ | ArrayNew _ -> `Pure (2, 1) | Select _ | VecTernOp _ | VecBitselect -> `Pure (3, 1) (* Wide integer arithmetic: pure, but produces a two-limb result. *) | Add128 | Sub128 -> `Pure (4, 2) | MulWide _ -> `Pure (2, 2) (* Allocations are effect-free and non-trapping (a dropped allocation is dead code): [array.new_fixed] takes its element count as an explicit immediate — unlike [struct.new], whose arity comes from the type — and [struct.new_default]/[array.new]/[array.new_default] above have a fixed arity too. *) | ArrayNewFixed (_, n) -> `Pure (Uint32.to_int n, 1) (* [struct.new] takes one operand per field; the arity comes from the type, looked up the same way folding does. [struct.new_desc] adds a descriptor operand. An unresolvable type falls through to [`Unhandled]. *) | StructNew idx -> ( match struct_arity idx with | Some n -> `Pure (n, 1) | None -> `Unhandled) | StructNewDesc idx -> ( match struct_arity idx with | Some n -> `Pure (n + 1, 1) | None -> `Unhandled) (* [nop] neither pops nor pushes, so it leaves the tracked stack unchanged rather than clearing it. *) | Nop -> `Neutral (* Has a side effect, transfers control, or may trap — never pure. *) | Unreachable | Throw _ | ThrowRef | Br _ | Br_if _ | Br_table _ | Br_on_null _ | Br_on_non_null _ | Br_on_cast _ | Br_on_cast_fail _ | Br_on_cast_desc_eq _ | Br_on_cast_desc_eq_fail _ | Return | Call _ | CallRef _ | CallIndirect _ | ReturnCall _ | ReturnCallRef _ | ReturnCallIndirect _ | ContNew _ | ContBind _ | Suspend _ | Resume _ | ResumeThrow _ | ResumeThrowRef _ | Switch _ | LocalSet _ | LocalTee _ | GlobalSet _ | Load _ | LoadS _ | Store _ | StoreS _ | Atomic _ | AtomicFence | MemoryGrow _ | MemoryFill _ | MemoryCopy _ | MemoryInit _ | DataDrop _ | TableGet _ | TableSet _ | TableGrow _ | TableFill _ | TableCopy _ | TableInit _ | ElemDrop _ | RefAsNonNull | RefCast _ | RefCastDescEq _ | RefGetDesc _ | StructGet _ | StructSet _ | ArrayNewData _ | ArrayNewElem _ | ArrayGet _ | ArraySet _ | ArrayLen | ArrayFill _ | ArrayCopy _ | ArrayInitData _ | ArrayInitElem _ | I31Get _ | VecLoad _ | VecStore _ | VecLoadLane _ | VecStoreLane _ | VecLoadSplat _ -> `Impure (* Possibly pure, but not modelled here; clears the stack conservatively, kept distinct from [`Impure] to flag as future work. The block forms would need a whole-body purity analysis (and reasoning about branches escaping the block) to be treated as a value producer; [Drop]/[Folded] are handled structurally in [step]; the rest are Wax extensions. *) | Block _ | Loop _ | If _ | TryTable _ | Try _ | Hinted _ | Drop | Folded _ | String _ | Char _ | If_annotation _ -> `Unhandled in let rec drop_n n l = if n <= 0 then l else match l with [] -> [] | _ :: t -> drop_n (n - 1) t in let rec walk instrs = (* Dead code: after the first unconditional divergence, the next statement (if any) can never be reached. Reported once, at that statement. *) let rec dead = function | a :: (b :: _ as rest) -> if is_diverging a then Error.dead_code diagnostics ~location:b.info ~related: [ { Wax_utils.Diagnostic.location = a.info; message = Wax_utils.Message.text "Control never returns from here."; }; ] else dead rest | _ -> () in dead instrs; ignore (List.fold_left step [] instrs : lint_val list) and step st (i : _ Ast.Text.instr) = match i.desc with | Const (I32 s) | Const (I64 s) -> ( match lint_int_value s with Some n -> LInt n :: st | None -> []) | Const (F32 s) | Const (F64 s) -> ( match lint_float_value s with Some f -> LFloat f :: st | None -> []) (* A bare read is pure; track its resolved index so a comparison of two reads of the same variable is recognised as identical operands. *) | LocalGet idx -> ( match Sequence.get_index_opt ctx.locals idx with | Some i -> LLocal i :: st | None -> LPure :: st) | GlobalGet idx -> ( match Sequence.get_index_opt ctx.modul.globals idx with | Some i -> LGlobal i :: st | None -> LPure :: st) | Folded (op, operands) -> (* Folded form wraps every instruction, even a leaf constant, as [Folded (Const n, [])]. Flatten to "operands then head": process the operands, then the head as if it were the next flat instruction (so a folded constant pushes, and a folded operator checks and clears). *) let st' = List.fold_left step st operands in step st' op | Hinted (_, inner) -> step st inner | _ -> ( check_op i st; recurse i; (* A pure operator whose operands are all pure yields pure results (so a later [drop] of one is flagged too); [local.get]/[global.get] and other zero-arity producers push a pure marker; anything else clears. *) match classify i.desc with | `Pure (consumed, produced) when List.length st >= consumed -> List.init produced (fun _ -> LPure) @ drop_n consumed st | `Neutral -> st | `Pure _ | `Impure | `Unhandled -> []) and recurse (i : _ Ast.Text.instr) = match i.desc with | Block { block; _ } | Loop { block; _ } | TryTable { block; _ } -> walk block.desc | If { if_block; else_block; _ } -> walk if_block.desc; walk else_block.desc | Try { block; catches; catch_all; _ } -> walk block.desc; List.iter (fun (_, b) -> walk b.Ast.desc) catches; Option.iter (fun b -> walk b.Ast.desc) catch_all | _ -> () in (* The [eager-select] lint. A [select] evaluates both of its value operands, so a trapping or effectful operation among them runs even when the condition picks the other one (the footgun behind Wax's [?:]). Reuses [classify]'s purity table: a hazard is any [`Impure] operator except the casts already covered by other lints. Only handles the folded form, where each value operand is a distinct operand subtree — an unfolded [select] leaves its operands on the flat stream, out of reach here. *) let is_control (d : _ Ast.Text.instr_desc) = match d with | Block _ | Loop _ | If _ | TryTable _ | Try _ | Select _ | Br _ | Br_if _ | Br_table _ | Br_on_null _ | Br_on_non_null _ | Br_on_cast _ | Br_on_cast_fail _ | Br_on_cast_desc_eq _ | Br_on_cast_desc_eq_fail _ | Return | Folded _ | Hinted _ -> true | _ -> false in let is_eager_hazard (d : _ Ast.Text.instr_desc) = match d with (* Plain casts / trapping numeric conversions are reported by [cast-always-fails] and [constant-trap]; exclude them so the hazard set matches the Wax typer's [find_eager_hazard]. *) | UnOp (I32 (Trunc _) | I64 (Trunc _)) | RefCast _ -> false | _ -> ( match classify d with `Impure -> true | _ -> false) in (* The location of a hazard reached on the eagerly-evaluated spine of a [select] value operand, descending through pure operators but stopping at any nested control construct. *) let rec has_hazard (i : _ Ast.Text.instr) = match i.desc with | Folded (op, operands) -> if is_control op.desc then None else if is_eager_hazard op.desc then Some op.info else List.find_map has_hazard operands | Hinted (_, inner) -> has_hazard inner | d -> if (not (is_control d)) && is_eager_hazard d then Some i.info else None in let rec sel_walk (i : _ Ast.Text.instr) = (match i.desc with | Folded (({ desc = Select _; _ } as sel), [ v1; v2; _cond ]) -> List.iter (fun operand -> match has_hazard operand with | Some location -> Error.eager_select diagnostics ~location ~select:sel.info | None -> ()) [ v1; v2 ] | _ -> ()); match i.desc with | Folded (op, operands) -> List.iter sel_walk operands; sel_walk op | Block { block; _ } | Loop { block; _ } | TryTable { block; _ } -> List.iter sel_walk block.desc | If { if_block; else_block; _ } -> List.iter sel_walk if_block.desc; List.iter sel_walk else_block.desc | Try { block; catches; catch_all; _ } -> List.iter sel_walk block.desc; List.iter (fun (_, b) -> List.iter sel_walk b.Ast.desc) catches; Option.iter (fun b -> List.iter sel_walk b.Ast.desc) catch_all | Hinted (_, inner) -> sel_walk inner | _ -> () in walk instrs; List.iter sel_walk instrs let functions ?(warn_unused = true) ctx fields = List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Func { id = _; typ; locals = locs; instrs; exports } -> let>@ func_typ = let*@ typ = typeuse ctx.diagnostics ctx.types typ in match (Types.get_subtype ctx.subtyping_info typ).typ with | Func typ -> Some typ | _ -> Error.not_function_type ctx.diagnostics ~location:field.info; None in let return_types = func_typ.results in let return_source = snd (functype_sources (typeuse_functype ctx.types typ)) in let locals = Sequence.make "local" in let initialized_locals = ref IntSet.empty in let i = ref 0 in (match typ with | _, Some { params; _ } -> Array.iter (fun p -> let id, typ = p.Ast.desc in initialized_locals := IntSet.add !i !initialized_locals; incr i; let interned = match valtype ctx.diagnostics ctx.types typ with | None -> (* Dummy value *) Ref { nullable = false; typ = None_ } | Some typ' -> typ' in Sequence.register locals id (interned, Plain typ)) params | _ -> (* No inline parameter list: take the parameters' source types from the referenced function type's definition. *) let param_source = fst (functype_sources (typeuse_functype ctx.types typ)) in Array.iteri (fun j typ -> initialized_locals := IntSet.add !i !initialized_locals; incr i; let source = param_source.(j) in Sequence.register locals None (typ, source)) func_typ.params); (* The locals declared by the function (not its parameters), recorded as (index, optional name, declaration location) so an unread one can be reported as unused after the body is validated. *) let declared_locals = ref [] in List.iter (fun e -> let id, typ = e.Ast.desc in let typ' = match valtype ctx.diagnostics ctx.types typ with | None -> (* Dummy value *) Ref { nullable = true; typ = Any } | Some typ -> typ in if is_defaultable typ' then initialized_locals := IntSet.add !i !initialized_locals; (* Point a named local's warning at its name; an unnamed one at the whole declaration. *) let location = match id with Some id -> id.Ast.info | None -> e.Ast.info in declared_locals := (!i, Option.map (fun id -> id.Ast.desc) id, location) :: !declared_locals; incr i; Sequence.register locals id (typ', Plain typ)) locs; let ctx = { locals; control_types = [ (None, return_types, return_source, ref false) ]; return_types; return_source; modul = ctx; initialized_locals = !initialized_locals; used_locals = ref IntSet.empty; label_decls = ref []; } in with_empty_stack ctx.modul field.info (let* () = instructions ctx instrs in pop_args ctx field.info (*ZZZ*) ~source:return_source return_types); (* A named local whose name starts with [_] is intentionally unused; unnamed locals are always reported. *) if warn_unused then List.iter (fun (idx, name, location) -> if (not (IntSet.mem idx !(ctx.used_locals))) && not (match name with | Some n -> String.length n > 0 && n.[0] = '_' | None -> false) then Error.unused_local ctx.modul.diagnostics ~location name) (List.rev !declared_locals); (* A named block label never branched to. A name starting with [_] is intentionally unused. *) if warn_unused then List.iter (fun ((name : Ast.Text.name), used) -> if (not !used) && not (String.length name.desc > 0 && name.desc.[0] = '_') then Error.unused_label ctx.modul.diagnostics ~location:name.info name.desc) (List.rev !(ctx.label_decls)); if warn_unused then lint_body ctx instrs; register_exports ctx.modul exports | _ -> ()) fields let exports ctx fields = List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Export { name; kind; index } -> ( register_exports ctx [ name ]; (* An exported field is externally reachable, so mark it used for the [unused-field] warning. *) let mark used seq = Option.iter (fun i -> Hashtbl.replace used i ()) (Sequence.get_index_opt seq index) in match kind with | Func -> ignore (Sequence.get ctx.diagnostics ctx.functions index); mark ctx.used_functions ctx.functions | Memory -> ignore (Sequence.get ctx.diagnostics ctx.memories index) | Table -> ignore (Sequence.get ctx.diagnostics ctx.tables index) | Tag -> ignore (Sequence.get ctx.diagnostics ctx.tags index) | Global -> ignore (Sequence.get ctx.diagnostics ctx.globals index); mark ctx.used_globals ctx.globals) | _ -> ()) fields let start ctx fields = List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Start idx -> ( let*? ty, _, _, _ = Sequence.get ctx.diagnostics ctx.functions idx in (* The start function is externally reachable. *) Option.iter (fun i -> Hashtbl.replace ctx.used_functions i ()) (Sequence.get_index_opt ctx.functions idx); match (Types.get_subtype ctx.subtyping_info ty).typ with | Struct _ | Array _ | Cont _ -> Error.not_function_type ctx.diagnostics ~location:idx.info | Func { params; results } -> if not (params = [||] && results = [||]) then Error.start_function_signature ctx.diagnostics ~location:idx.info) | _ -> ()) fields (* Report module-defined functions and globals that are never referenced, exported, or used as the start function (the module-level analog of an unused local), and likewise for imports. Uses are collected during validation into [used_functions] / [used_globals]; a name starting with [_] is intentionally unused. Runs after every other pass so all references have been seen. *) let unused_fields ctx = if not ctx.warn_unused then () else let report emit used kind decls = List.iter (fun (idx, (name : Ast.Text.name option), location) -> if (not (Hashtbl.mem used idx)) && not (match name with | Some n -> String.length n.desc > 0 && n.desc.[0] = '_' | None -> false) then emit ctx.diagnostics ~location kind (Option.map (fun (n : Ast.Text.name) -> n.desc) name)) (List.rev decls) in report Error.unused_field ctx.used_functions "function" ctx.defined_functions; report Error.unused_field ctx.used_globals "global" ctx.defined_globals; report Error.unused_import ctx.used_functions "function" ctx.imported_functions; report Error.unused_import ctx.used_globals "global" ctx.imported_globals (*** Whole-module validation ***) (* Syntactic well-formedness checks that the stack-based validation does not cover: duplicate identifiers in each namespace, an inline type annotation that disagrees with the type it names, duplicate parameter/local names, and a second start function. Type references are resolved through [ctx], the type context the rest of validation has already built, which handles recursive and forward references correctly. *) let check_syntax ctx lst = let types = Hashtbl.create 16 in let functions = Hashtbl.create 16 in let memories = Hashtbl.create 16 in let tables = Hashtbl.create 16 in let globals = Hashtbl.create 16 in let = Hashtbl.create 16 in let elems = Hashtbl.create 16 in let datas = Hashtbl.create 16 in let check_unbound tbl kind id = let>@ id : Ast.Text.name = id in if Hashtbl.mem tbl id.desc then Error.index_already_bound ctx.diagnostics ~location:id.info kind id else Hashtbl.add tbl id.desc () in let iter_instrs f instrs = List.iter (Ast_utils.iter_instr (fun i -> f i.Ast.desc)) instrs in (* An inline type annotation [(type idx) (param ...) (result ...)] must name a function type whose signature equals the inline one. *) let check_inline_type idx target = let>@ gidx = resolve_type_index ctx.diagnostics ctx.types idx in match (Types.get_subtype ctx.subtyping_info gidx).typ with | Func f -> ( match functype ctx.diagnostics ctx.types target with | Some f' -> if f <> f' then Error.inline_function_type_mismatch ctx.diagnostics ~location:idx.Ast.info f | None -> ()) | Struct _ | Array _ | Cont _ -> Error.expected_func_type ctx.diagnostics ~location:idx.Ast.info idx in let check_instr_inline desc = let check_typeuse = function | Ast.Text.Typeuse (Some idx, Some ft) -> check_inline_type idx ft | _ -> () in match desc with | Ast.Text.Block { typ = Some t; _ } | Ast.Text.Loop { typ = Some t; _ } | Ast.Text.If { typ = Some t; _ } | Ast.Text.Try { typ = Some t; _ } | Ast.Text.TryTable { typ = Some t; _ } -> check_typeuse t | CallIndirect (_, (Some idx, Some ft)) -> check_inline_type idx ft | ReturnCallIndirect (_, (Some idx, Some ft)) -> check_inline_type idx ft | _ -> () in let check_duplicate_locals typ locals = let param_ids = match snd typ with | Some { Ast.Text.params; _ } -> Array.to_list (Array.map (fun p -> fst p.Ast.desc) params) | None -> [] in let local_ids = List.map (fun e -> fst e.Ast.desc) locals in let seen = Hashtbl.create 16 in List.iter (fun id -> let*? id : Ast.Text.name = id in if Hashtbl.mem seen id.desc then Error.duplicate_local ctx.diagnostics ~location:id.Ast.info id.desc else Hashtbl.add seen id.desc ()) (param_ids @ local_ids) in let check_import id (desc : Ast.Text.importdesc) = let tbl, kind = match desc with | Func _ -> (functions, "function") | Memory _ -> (memories, "memory") | Table _ -> (tables, "table") | Global _ -> (globals, "global") | Tag _ -> (tags, "tag") in check_unbound tbl kind id; match desc with | Func { typ = Some idx, Some sign; _ } -> check_inline_type idx sign | Tag (Some idx, Some sign) -> check_inline_type idx sign | _ -> () in List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Types lst -> Array.iter (fun e -> check_unbound types "type" (fst e.Ast.desc); match (snd e.Ast.desc).Ast.Text.typ with | Ast.Text.Types.Func _ | Array _ | Cont _ -> () | Struct lst -> let fields = Hashtbl.create 16 in Array.iter (fun e -> check_unbound fields "field" (fst e.Ast.desc)) lst) lst | Import { id; desc; _ } -> check_import id desc | Import_group1 { items; _ } -> List.iter (fun (_, id, desc) -> check_import id desc) items | Import_group2 { desc; items; _ } -> List.iter (fun (_, id) -> check_import id desc) items | Func { id; typ; locals; instrs; _ } -> check_unbound functions "function" id; (match typ with | Some idx, Some sign -> check_inline_type idx sign | _ -> ()); check_duplicate_locals typ locals; iter_instrs check_instr_inline instrs | Memory { id; _ } -> check_unbound memories "memory" id | Table { id; _ } -> check_unbound tables "table" id | Tag { id; typ = Some idx, Some sign; _ } -> check_unbound tags "tag" id; check_inline_type idx sign | Tag { id; _ } -> check_unbound tags "tag" id | Global { id; _ } -> check_unbound globals "global" id | Export _ | Start _ -> () | Elem { id; _ } -> check_unbound elems "elem" id | Data { id; _ } -> check_unbound datas "data" id | String_global { id; _ } -> check_unbound globals "global" (Some id) | Feature_annotation _ | Module_if_annotation _ -> ()) lst; match List.filter (fun field -> match field.Ast.desc with Ast.Text.Start _ -> true | _ -> false) lst with | _ :: second :: _ -> Error.multiple_start ctx.diagnostics ~location:second.Ast.info | _ -> () let validate_configuration ?(warn_unused = true) ?(features = Wax_utils.Feature.default ()) diagnostics (_, fields) = let type_context = { types = Types.create (); last_index = 0; index_mapping = Hashtbl.create 16; label_mapping = Hashtbl.create 16; type_defs = Hashtbl.create 16; descriptor_source = Hashtbl.create 16; features; } in List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Types rectype -> add_type diagnostics type_context rectype | _ -> ()) fields; collect_implicit_types diagnostics type_context fields; (* Make the type context available to [push] for go-to-type-definition recording (editor mode only; reset in [f]). *) if !recorded_types <> None then sink_type_context := Some type_context; (* Register the implicit [<string>] array type ([mut i8]) up front, so that validating an unnamed [@string] — which looks the type up via [string_type] ([add_rectype], idempotent) — gets an index within [subtyping_info] instead of one appended past the snapshot taken here. *) ignore (string_type type_context : Types.Id.t); let ctx = { diagnostics; types = type_context; subtyping_info = Types.subtyping_info type_context.types; functions = Sequence.make "function"; memories = Sequence.make "memory"; tables = Sequence.make "table"; globals = Sequence.make "global"; tags = Sequence.make "tag"; data = Sequence.make "data segment"; elem = Sequence.make "elem segment"; exports = Hashtbl.create 16; refs = Hashtbl.create 16; used_functions = Hashtbl.create 16; used_globals = Hashtbl.create 16; defined_functions = []; defined_globals = []; imported_functions = []; imported_globals = []; warn_unused; } in check_type_definitions ctx; build_initial_env ctx fields; let ctx = { ctx with subtyping_info = Types.subtyping_info type_context.types } in check_syntax ctx fields; tables_and_memories ctx fields; globals ctx fields; segments ctx fields; declared_func_exports ctx fields; functions ~warn_unused ctx fields; exports ctx fields; start ctx fields; unused_fields ctx (* Path-sensitive validation of conditional annotations. A module containing [(@if ...)] conditionals denotes one concrete module per "configuration" (a choice of branch at every reachable conditional). We explore every reachable configuration (via {!Cond_explore.check_all}), specializing the module for each — splicing in the selected branches to obtain a conditional-free module — validating it with {!validate_configuration}, and reporting each distinct error once, annotated with the minimal assumption under which it occurs. *) (*** Conditional compilation and entry point ***) (* Walk through every nested instruction (branch hints included) via the canonical [Ast_utils.fold_instr], so a conditional buried inside a branch-hinted branch is not missed. *) let instr_has_conditional (i : _ Ast.Text.instr) = Ast_utils.fold_instr (fun found (i : _ Ast.Text.instr) -> found || match i.desc with If_annotation _ -> true | _ -> false) false i let expr_has_conditional e = List.exists instr_has_conditional e (* Exhaustive over [modulefield]: every instruction list a field can carry is inspected (including the offset expression of an active [data]/[elem] segment), and a new field variant is a compile error rather than a silent miss. Must stay in sync with [specialize] below, which walks the same lists. *) let field_has_conditional (f : (_ Ast.Text.modulefield, _) Ast.annotated) = match f.desc with | Module_if_annotation _ -> true | Func { instrs; _ } -> expr_has_conditional instrs | Global { init; _ } -> expr_has_conditional init | Table { init; _ } -> ( match init with | Init_default -> false | Init_expr e -> expr_has_conditional e | Init_segment segs -> List.exists expr_has_conditional segs) | Elem { init; mode; _ } -> ( List.exists expr_has_conditional init || match mode with | Active (_, offset) -> expr_has_conditional offset | Passive | Declare -> false) | Data { mode; _ } -> ( match mode with | Active (_, offset) -> expr_has_conditional offset | Passive -> false) | Types _ | Import _ | Import_group1 _ | Import_group2 _ | Memory _ | Tag _ | Export _ | Start _ | String_global _ | Feature_annotation _ -> false (* Specialize a module for one configuration: resolve every conditional using the assumption [asm], splicing in the selected branch. Undetermined conditionals select [@then] and [enqueue] the [@else] configuration; each selected branch literal is passed to [record] to build the configuration's full assumption. *) let specialize env diagnostics ~enqueue ~record asm0 fields = (* Resolve one conditional and return both the specialized branch and the assumption that holds afterwards. Each branch is taken only if it is reachable under [asm] (its conjunction with the branch condition is satisfiable); an unreachable branch is pruned, so we never explore an infeasible configuration. The surviving assumption is threaded into the following siblings, so e.g. once [cond1] forces [$wasi], a sibling [(@if (not $wasi) …)] has its [@then] pruned. *) let choose asm cond ~location ~then_branch ~else_branch = let c = Cond_solver.of_cond env diagnostics ~location cond in let then_asm = Cond_solver.and_ asm c and else_asm = Cond_solver.and_ asm (Cond_solver.not_ c) in if not (Cond_solver.is_satisfiable then_asm) then ( record (Cond_solver.not_ c); (else_branch else_asm, else_asm)) else if not (Cond_solver.is_satisfiable else_asm) then ( record c; (then_branch then_asm, then_asm)) else ( enqueue else_asm; record c; (then_branch then_asm, then_asm)) in let rec sfields asm fl = match fl with | [] -> [] | f :: rest -> let fields, asm = sfield asm f in fields @ sfields asm rest and sfield asm (f : (_ Ast.Text.modulefield, _) Ast.annotated) = match f.desc with | Module_if_annotation { cond; then_fields; else_fields } -> choose asm cond ~location:f.info ~then_branch:(fun asm' -> sfields asm' then_fields.desc) ~else_branch:(fun asm' -> match else_fields with Some e -> sfields asm' e.desc | None -> []) | Func { id; typ; locals; instrs; exports } -> let desc : _ Ast.Text.modulefield = Func { id; typ; locals; instrs = sinstrs asm instrs; exports } in ([ { f with desc } ], asm) | Global { id; typ; init; exports } -> let desc : _ Ast.Text.modulefield = Global { id; typ; init = sinstrs asm init; exports } in ([ { f with desc } ], asm) | Table { id; typ; init; exports } -> let init : _ Ast.Text.tableinit = match init with | Init_default -> Init_default | Init_expr e -> Init_expr (sinstrs asm e) | Init_segment segs -> Init_segment (List.map (sinstrs asm) segs) in let desc : _ Ast.Text.modulefield = Table { id; typ; init; exports } in ([ { f with desc } ], asm) | Elem { id; typ; init; mode } -> let mode : _ Ast.Text.elemmode = match mode with | Active (idx, e) -> Active (idx, sinstrs asm e) | (Passive | Declare) as mode -> mode in let desc : _ Ast.Text.modulefield = Elem { id; typ; init = List.map (sinstrs asm) init; mode } in ([ { f with desc } ], asm) | Data { id; init; mode } -> let mode : _ Ast.Text.datamode = match mode with | Active (idx, e) -> Active (idx, sinstrs asm e) | Passive as mode -> mode in ([ { f with desc = Data { id; init; mode } } ], asm) | Types _ | Import _ | Import_group1 _ | Import_group2 _ | Memory _ | Tag _ | Export _ | Start _ | String_global _ | Feature_annotation _ -> ([ f ], asm) and sinstrs asm l = match l with | [] -> [] | i :: rest -> let instrs, asm = sinstr asm i in instrs @ sinstrs asm rest and sinstr asm (i : _ Ast.Text.instr) = match i.desc with | If_annotation { cond; then_body; else_body } -> choose asm cond ~location:i.info ~then_branch:(fun asm' -> sinstrs asm' then_body.desc) ~else_branch:(fun asm' -> match else_body with Some e -> sinstrs asm' e.desc | None -> []) | desc -> ([ { i with desc = sstructured asm desc } ], asm) and sstructured asm (desc : _ Ast.Text.instr_desc) = match desc with | Block b -> Block { b with block = { b.block with desc = sinstrs asm b.block.desc } } | Loop b -> Loop { b with block = { b.block with desc = sinstrs asm b.block.desc } } | If b -> If { b with if_block = { b.if_block with desc = sinstrs asm b.if_block.desc }; else_block = { b.else_block with desc = sinstrs asm b.else_block.desc }; } | TryTable b -> TryTable { b with block = { b.block with desc = sinstrs asm b.block.desc } } | Try b -> Try { b with block = { b.block with desc = sinstrs asm b.block.desc }; catches = List.map (fun (idx, l) -> (idx, { l with Ast.desc = sinstrs asm l.Ast.desc })) b.catches; catch_all = Option.map (fun b -> { b with Ast.desc = sinstrs asm b.Ast.desc }) b.catch_all; } | Folded (h, l) -> Folded ({ h with desc = sstructured asm h.desc }, sinstrs asm l) | Hinted (hint, inner) -> Hinted (hint, { inner with desc = sstructured asm inner.desc }) (* Every instruction that carries no nested instruction is returned as-is. Enumerated rather than caught by a wildcard so a future instruction that nests others is a compile error here instead of silently escaping specialization. *) | ( Unreachable | Nop | Throw _ | ThrowRef | ContNew _ | ContBind _ | Suspend _ | Resume _ | ResumeThrow _ | ResumeThrowRef _ | Switch _ | Br _ | Br_if _ | Br_table _ | Br_on_null _ | Br_on_non_null _ | Br_on_cast _ | Br_on_cast_fail _ | Br_on_cast_desc_eq _ | Br_on_cast_desc_eq_fail _ | Return | Call _ | CallRef _ | ReturnCall _ | ReturnCallRef _ | Drop | Select _ | LocalGet _ | LocalSet _ | LocalTee _ | GlobalGet _ | GlobalSet _ | Load _ | LoadS _ | Store _ | StoreS _ | Atomic _ | AtomicFence | MemorySize _ | MemoryGrow _ | MemoryFill _ | MemoryCopy _ | MemoryInit _ | DataDrop _ | TableGet _ | TableSet _ | TableSize _ | TableGrow _ | TableFill _ | TableCopy _ | TableInit _ | ElemDrop _ | RefNull _ | RefFunc _ | RefIsNull | RefAsNonNull | RefEq | RefTest _ | RefCast _ | RefCastDescEq _ | RefGetDesc _ | StructNew _ | StructNewDefault _ | StructNewDesc _ | StructNewDefaultDesc _ | StructGet _ | StructSet _ | ArrayNew _ | ArrayNewDefault _ | ArrayNewFixed _ | ArrayNewData _ | ArrayNewElem _ | ArrayGet _ | ArraySet _ | ArrayLen | ArrayFill _ | ArrayCopy _ | ArrayInitData _ | ArrayInitElem _ | RefI31 | I31Get _ | Const _ | UnOp _ | BinOp _ | Add128 | Sub128 | MulWide _ | I32WrapI64 | I64ExtendI32 _ | F32DemoteF64 | F64PromoteF32 | ExternConvertAny | AnyConvertExtern | VecBitselect | VecConst _ | VecUnOp _ | VecBinOp _ | VecTest _ | VecShift _ | VecBitmask _ | VecLoad _ | VecStore _ | VecLoadLane _ | VecStoreLane _ | VecLoadSplat _ | VecExtract _ | VecReplace _ | VecSplat _ | VecShuffle _ | VecTernOp _ | Char _ | CallIndirect _ | ReturnCallIndirect _ | String _ | If_annotation _ ) as desc -> desc in sfields asm0 fields (* WebAssembly requires every import to precede all non-import definitions (functions, tables, memories, globals, tags). Report any import that follows such a definition. *) let check_import_order diagnostics fields = ignore (List.fold_left (fun can_import (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match (can_import, field.desc) with | Some previous, (Import _ | Import_group1 _ | Import_group2 _) -> Error.import_after_definition diagnostics ~location:field.info previous; can_import | None, Func _ -> Some "function" | None, Memory _ -> Some "memory" | None, Table _ -> Some "table" | None, Tag _ -> Some "tag" | None, Global _ -> Some "global" | None, String_global _ -> Some "string" | ( Some _, (Func _ | Memory _ | Table _ | Tag _ | Global _ | String_global _) ) | None, (Import _ | Import_group1 _ | Import_group2 _) | ( _, ( Types _ | Export _ | Start _ | Elem _ | Data _ | Feature_annotation _ | Module_if_annotation _ ) ) -> can_import) None fields) (* Apply the module's [(@feature "…")] declarations to [features]: each declared feature is enabled, in union with the command-line configuration — unless the command line explicitly disabled it, which is a conflict reported once, at the annotation. Runs at the entry point, before anything consults [is_enabled]. Only top-level annotations count: the annotation states a fact about the whole module, so it is not conditional. Mirrors the Wax typer's [apply_declared_features]. *) let apply_declared_features diagnostics features fields = List.iter (fun (field : (_ Ast.Text.modulefield, _) Ast.annotated) -> match field.desc with | Ast.Text.Feature_annotation name -> ( let location = name.Ast.info in match Wax_utils.Feature.of_name name.Ast.desc with | None -> Error.unknown_feature diagnostics ~location name.Ast.desc | Some feature -> if Wax_utils.Feature.explicitly_disabled features feature then Error.feature_conflict diagnostics ~location feature; (* Enable it even on a conflict: the error has been reported once, at the annotation; without this every gated construct below would error too. *) Wax_utils.Feature.declare features feature) | _ -> ()) fields let f ?(warn_unused = true) ?(features = Wax_utils.Feature.default ()) ?record_types diagnostics ((name, fields) as modul) = Wax_utils.Debug.timed "validate" @@ fun () -> recorded_types := record_types; sink_config := 0; Fun.protect ~finally:(fun () -> recorded_types := None; sink_type_context := None) @@ fun () -> apply_declared_features diagnostics features fields; check_import_order diagnostics fields; if not (List.exists field_has_conditional fields) then validate_configuration ~warn_unused ~features diagnostics modul else (* Tag each explored configuration's recorded types with a distinct index, so a config-varying span's alternatives stay separable from a single configuration's multi-result tuple. *) let config = ref (-1) in Cond_explore.check_all diagnostics ?truncation_location: (match fields with f :: _ -> Some f.Ast.info | [] -> None) ~specialize:(fun env asm ~enqueue ~record -> (name, specialize env diagnostics ~enqueue ~record asm fields)) ~check:(fun diagnostics modul -> incr config; sink_config := !config; validate_configuration ~warn_unused ~features diagnostics modul) ()
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>