mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 14:48:40 +00:00
Compare commits
664 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 277b3f71e3 | |||
| a7a69d38a4 | |||
| 4210200070 | |||
| 3883550c44 | |||
| 5ce072b827 | |||
| 4029b838ce | |||
| 3fbab2ede3 | |||
| a29e1d43f1 | |||
| ee663ccb96 | |||
| cd867b72b9 | |||
| 8aa223aed9 | |||
| 3a4e0c94e1 | |||
| 0a0981a475 | |||
| 2f57e4ff6d | |||
| 5e33dccf1f | |||
| f4fdf8dce9 | |||
| 9c3f9537be | |||
| f5b7925047 | |||
| 50401c05e8 | |||
| 7914a729a5 | |||
| 8eedd2b04b | |||
| 60b483bd3b | |||
| 322abf6d22 | |||
| b85cb55aab | |||
| 03d83181ca | |||
| 31ee1637df | |||
| b7bcb256d7 | |||
| 7516c5d69e | |||
| c6ae1c58fc | |||
| 0c2fefa09b | |||
| ea2a6b70b2 | |||
| d054d4d512 | |||
| e7e1b755c9 | |||
| 38cd5b80ec | |||
| 57ff3a5ca0 | |||
| ecf7014f7e | |||
| 7c3a22d289 | |||
| 2c7ea98ccf | |||
| 57b8f7e667 | |||
| db0efc52c7 | |||
| 94b075e423 | |||
| ce5d52870f | |||
| dd6adcacb6 | |||
| 610a20c4d5 | |||
| daf5d0b63a | |||
| 233f5c6af2 | |||
| ca2f25ed48 | |||
| cc1a95a489 | |||
| 431e223803 | |||
| 1b46324456 | |||
| 751ac85488 | |||
| 512fb1dae6 | |||
| 0d20afa85f | |||
| c87bb0e9cc | |||
| 6afceb5621 | |||
| aa053b5fb0 | |||
| 0ed34e3cb0 | |||
| 78a36f7724 | |||
| a743580734 | |||
| e2178ce682 | |||
| b2ef7299b7 | |||
| 14ce531498 | |||
| 21c76c0cb0 | |||
| fbd72a4b2c | |||
| 93e0636c03 | |||
| ef77b645b9 | |||
| 0b939f93bc | |||
| 798085e866 | |||
| 69bf6e3c89 | |||
| 8f8d83bffe | |||
| 998f01608c | |||
| 84279ab2ec | |||
| a888d6245d | |||
| f80efa5b8b | |||
| 29f7ebc63e | |||
| 72e15af1fa | |||
| 6cb604c752 | |||
| cb01d4d63e | |||
| fe21650010 | |||
| ebd4969cde | |||
| 32a7b8cc4e | |||
| b4fa658705 | |||
| ee4e42ba1f | |||
| c6678525a9 | |||
| 8d75b58bdf | |||
| ec97313239 | |||
| e046b23773 | |||
| 0099d47002 | |||
| 5c3ad004ab | |||
| 61be9189f1 | |||
| 1e2791b216 | |||
| d86dd642b3 | |||
| 21b89ef327 | |||
| 39c8711332 | |||
| edc9319e0d | |||
| fcd88356db | |||
| e536676a67 | |||
| 342c438586 | |||
| 6b7ecf1f52 | |||
| bd7ab8ddd5 | |||
| 302e72e84f | |||
| 55ca5287fe | |||
| 11ee0969b6 | |||
| 81dbbc89d3 | |||
| a4f9e5e552 | |||
| f9f2349850 | |||
| f1b15db258 | |||
| 12e314ccc9 | |||
| e594dbc133 | |||
| bb08a25edc | |||
| b7b7713526 | |||
| 92150bd1c5 | |||
| d0633f6228 | |||
| d46933abf2 | |||
| 26625f5a22 | |||
| 3e109fca5f | |||
| 763a86cd8e | |||
| 13a3c4b155 | |||
| 9fa667ce63 | |||
| 8bd39d2fc2 | |||
| c685e0696a | |||
| 9db8826b3b | |||
| 0aed62efe4 | |||
| 2b1a72d112 | |||
| a6837f05a4 | |||
| c35ce761aa | |||
| d2963b153e | |||
| f9293645af | |||
| 38305399a3 | |||
| 8f2082df69 | |||
| da8eb521e1 | |||
| dc53a59c49 | |||
| f21fdd1f76 | |||
| 5e719b0d3d | |||
| 9eb13884de | |||
| 85031d6df7 | |||
| 58b44f9f17 | |||
| c0ea21ece7 | |||
| 81edf577a3 | |||
| 484bb8f13d | |||
| ae1e7731a4 | |||
| 518745ea79 | |||
| 38b14706e2 | |||
| 747336f0a9 | |||
| e4de7b4957 | |||
| d5c0083085 | |||
| e6fbad13c6 | |||
| 62bda8e129 | |||
| 51d6ffb3be | |||
| 964aa7058a | |||
| 0f9d2ac0e4 | |||
| 5264469cbd | |||
| c096f35224 | |||
| 929cd767c1 | |||
| e1df2510d4 | |||
| 2ebaa9f520 | |||
| 5159eab694 | |||
| 75dfb26452 | |||
| 971da0bc09 | |||
| 0420842f4d | |||
| d30f8b6ed6 | |||
| 1f6b4a5e7b | |||
| 51fc78c100 | |||
| e4f2b9c003 | |||
| cff4493ca0 | |||
| 93ac7cec0d | |||
| b4503c1e37 | |||
| 3e6410f323 | |||
| 0f69d016a0 | |||
| ef8c1a187d | |||
| 08d0dc0d25 | |||
| 039186a2a3 | |||
| d6c892fe7b | |||
| cfe6b9765f | |||
| 539495ef45 | |||
| b13c993565 | |||
| 449bfe04f3 | |||
| 1903cf23c9 | |||
| 9734f349a3 | |||
| d87ff838eb | |||
| a360c82b40 | |||
| 4a2faeb2c5 | |||
| 99f00d396d | |||
| 049c5ed3b1 | |||
| cce02cd046 | |||
| be7bbba4ca | |||
| e668c8c1a7 | |||
| d62c9696fb | |||
| 343146f35c | |||
| 0285171484 | |||
| 1a22d92217 | |||
| 8416bb61d8 | |||
| 85ad157f3f | |||
| b123ffcea4 | |||
| ec9fd3fb38 | |||
| 5127b9d65b | |||
| 74b20ca234 | |||
| 3d73ee77d3 | |||
| 6b0f2cd697 | |||
| e354a3523b | |||
| f79f6b0e62 | |||
| 47db50b273 | |||
| b0fce80b50 | |||
| 718746dd21 | |||
| 6cbaed75c8 | |||
| a6200920f7 | |||
| 79467baf12 | |||
| c31aef06ba | |||
| 6bd18af5ef | |||
| 1789570f52 | |||
| a536ddcda8 | |||
| 18242bc26a | |||
| 18f4ffd656 | |||
| 7238c0a16f | |||
| c177e4dbc0 | |||
| 1d2c17753a | |||
| 470cbd5f53 | |||
| 62d4a6a77f | |||
| 339301b709 | |||
| aea4f57a2b | |||
| 2a17a3e56c | |||
| 34011c4800 | |||
| f8206b9bcc | |||
| b01ce95cb5 | |||
| 0658e4896f | |||
| 9d87d658ba | |||
| ec95d3560f | |||
| bf076aed61 | |||
| dee5602e56 | |||
| 4ef340f228 | |||
| 843a7ac445 | |||
| 8ed7d197a7 | |||
| 39ce7111bd | |||
| 06df31944c | |||
| 3ba8bc92cd | |||
| 0e7a129de7 | |||
| aeddcd9c5f | |||
| bf0b05e32c | |||
| 348a02d697 | |||
| 7273a2b5fd | |||
| 44bb381220 | |||
| 2e6b92fc56 | |||
| 51290e5842 | |||
| 22c35c8e31 | |||
| 2081380b5c | |||
| 3d0c6dd02d | |||
| 869e917dc6 | |||
| 41bcad9c19 | |||
| 73ab44c178 | |||
| b70b6076e3 | |||
| 7b41d92198 | |||
| 2f4f3bd1ba | |||
| 7cb44fb373 | |||
| 73ad825b67 | |||
| 539e1324c8 | |||
| 641dcd7c16 | |||
| 523d1f28cf | |||
| 782c57cc11 | |||
| c1d697f868 | |||
| 335fb71d2f | |||
| 5866a47e77 | |||
| 823c9c25cf | |||
| d15e32fb89 | |||
| b534dd67e0 | |||
| 6c02b4956c | |||
| 1681ed02d3 | |||
| 7e68980c39 | |||
| cf640290a3 | |||
| 78fec3719f | |||
| af4d0fe191 | |||
| 7caf0732fa | |||
| edcece33ca | |||
| 30bbdd5aeb | |||
| 03383760d6 | |||
| fce403b7a0 | |||
| fb33f3813d | |||
| 670fcf59d8 | |||
| cceb655874 | |||
| 13891d428f | |||
| 6c9bb96bca | |||
| 403d93560b | |||
| b344d65781 | |||
| c638f03b3c | |||
| 79bdd3f79a | |||
| 39ff13fd1a | |||
| de6f831983 | |||
| fb6571e405 | |||
| 29206cf0a4 | |||
| c2165f74d8 | |||
| 02ef64f012 | |||
| 5792f3a1cf | |||
| f63c389f1a | |||
| 7c24925aa7 | |||
| d7b7583e83 | |||
| 1869efe954 | |||
| 54dd75f7b3 | |||
| 27cbb53538 | |||
| 0704794def | |||
| f24a93c51d | |||
| c454568688 | |||
| d46bf2e5e0 | |||
| 9e1b4de999 | |||
| a4afc3b4b0 | |||
| 90076f9401 | |||
| afd49e7cdd | |||
| 4199be9780 | |||
| 86f1e6aec4 | |||
| f99c600ad8 | |||
| 15d3f5f609 | |||
| c8719f8d14 | |||
| e848f47ad4 | |||
| 3fe7ab19f6 | |||
| b5d61760f7 | |||
| 14bb90c3c0 | |||
| 1645f45c1a | |||
| 38b9c55ae6 | |||
| 497c74e4a9 | |||
| ae864bdf0c | |||
| e50885a6f2 | |||
| 478dd3a28d | |||
| 112b369636 | |||
| 43efe94041 | |||
| b5b2600b7b | |||
| 3f89fa0bee | |||
| a5d905f19b | |||
| d1f1f267a3 | |||
| d21ffc63b9 | |||
| 90f4b0a266 | |||
| a88530b785 | |||
| 18aaed63b9 | |||
| a413d5dfe9 | |||
| 4d5ec6c57b | |||
| 98f84a497d | |||
| 00c73d62ad | |||
| dbfaaf7c13 | |||
| 878b62bbe8 | |||
| 5d8f25a622 | |||
| c7413837aa | |||
| b1ec8eb2e0 | |||
| 98fbf7ff25 | |||
| af00e218a8 | |||
| b31d241388 | |||
| a6246e60f3 | |||
| 0a80da46b1 | |||
| 36f1517e8d | |||
| 04040453b4 | |||
| 2b453db4da | |||
| 29d3e10dc9 | |||
| 06c60b4ecf | |||
| 5c0a337c4f | |||
| 04e8f44370 | |||
| e02727679f | |||
| 138add2b96 | |||
| bf9dab36f7 | |||
| 6234bf9a88 | |||
| 32899d1cc3 | |||
| 0dfb336563 | |||
| d9ad500cf7 | |||
| 49dd2ce393 | |||
| 7df8e8db42 | |||
| 25011c5078 | |||
| 54867f901a | |||
| 1573826005 | |||
| ecd8c2d902 | |||
| a116fd0dc6 | |||
| a590d791bd | |||
| cb147b9475 | |||
| c830f878c6 | |||
| c7c874a487 | |||
| c528a168ee | |||
| 88b9c27dbf | |||
| 37ee4bea40 | |||
| ca4f251050 | |||
| 1e92e5f6c6 | |||
| 3eb9dca695 | |||
| 53794cf339 | |||
| da6c14481f | |||
| fb5516604d | |||
| e9f1ed701a | |||
| 6315db21f7 | |||
| 485a9284e7 | |||
| 602d3d7c78 | |||
| 409688e67a | |||
| d348db4a0d | |||
| 95ab7cb8d1 | |||
| 32de906f6d | |||
| eaab05fc43 | |||
| 4d1945b467 | |||
| fd9422d218 | |||
| bbbe7d43ce | |||
| b3f1dacbb9 | |||
| 5fa1e7163a | |||
| 4b1f92600f | |||
| ca39bc9f35 | |||
| f985f2c376 | |||
| f0936ffccb | |||
| 97d48e5c02 | |||
| a7c53cce06 | |||
| 2d224ae049 | |||
| 6c6fea5387 | |||
| 98bd947817 | |||
| 255f35671d | |||
| 8e88e560a1 | |||
| d45497691f | |||
| ad73986070 | |||
| 931f87f96a | |||
| 192a32f8d9 | |||
| 972f4254eb | |||
| 7d83e2ee5c | |||
| 0f2f73be53 | |||
| a2cc5715ba | |||
| 59d53182bb | |||
| 25c7bfd404 | |||
| 04f520040e | |||
| f57e9622fd | |||
| c25a7cc747 | |||
| 5e5ce98d42 | |||
| a3c4421f39 | |||
| 58565b42cc | |||
| ca7c849da3 | |||
| 6c1301688b | |||
| d05103668f | |||
| 4f7b23c2b7 | |||
| cfae2d4f9a | |||
| 55789fd2c2 | |||
| 478c592b13 | |||
| ab47cea055 | |||
| 98e70c9b19 | |||
| 7434e5a2c7 | |||
| 65c1978965 | |||
| e834d78871 | |||
| 03481789b0 | |||
| 73cf187552 | |||
| b40703e986 | |||
| 00ea0b1d57 | |||
| efa0410075 | |||
| 607d824211 | |||
| 8cc7c6d572 | |||
| cdba4fa8cc | |||
| 0565b7c0e0 | |||
| 444dded92c | |||
| 42785e08e8 | |||
| 2d633e3a28 | |||
| 64d048c47c | |||
| c8e231bc0b | |||
| b00cfc001e | |||
| e127ceac67 | |||
| e5453ebe27 | |||
| 0e267dd230 | |||
| 96e863f0f3 | |||
| 75298bb84b | |||
| e65592599c | |||
| e02f308d43 | |||
| bfe3f68647 | |||
| 2bb70812a8 | |||
| 293f4ea7bc | |||
| c3032660c9 | |||
| d94f42f6e2 | |||
| d8ac7ccaae | |||
| 64058c3efb | |||
| e107efa63f | |||
| 8e33f1c9eb | |||
| 1913cb76a5 | |||
| cd628bcde6 | |||
| d1f445735c | |||
| 28a2ad4757 | |||
| f2e8d7112c | |||
| 52d640967b | |||
| 87e48c1057 | |||
| b406b81416 | |||
| 3a458ec75c | |||
| 7764797061 | |||
| 5f9e339cf3 | |||
| c017ed2242 | |||
| d62a9e24e5 | |||
| b092856238 | |||
| 15d77119c9 | |||
| 95af444896 | |||
| 1f5e4e79aa | |||
| 9912dd6db1 | |||
| 42ec3e2469 | |||
| 4e610a0ee7 | |||
| af65c006e6 | |||
| 98f117fca4 | |||
| 4c95febeee | |||
| 793a3175d3 | |||
| 78d030aa7a | |||
| 62f9f61664 | |||
| 5b82125765 | |||
| 2f248bbf8b | |||
| c5ea1fde61 | |||
| 36dffb5554 | |||
| e8c4c4a865 | |||
| 8b79e82686 | |||
| 22eeed2da1 | |||
| cfbc9be9bf | |||
| 722a3a5c94 | |||
| ed2db8a26d | |||
| fd8938c634 | |||
| fa3dd41a4f | |||
| 003c96edc0 | |||
| ac54302301 | |||
| c43a41165a | |||
| 3145c2747e | |||
| 2c4b507d34 | |||
| ee167f15de | |||
| 422ebeeec7 | |||
| c93ddb630b | |||
| 3edcdb5f0d | |||
| ec325c0643 | |||
| 87c2ccb0b9 | |||
| 6e1eb28ed0 | |||
| 45cf2a5a1a | |||
| 423cae86df | |||
| ea6b65e360 | |||
| e3b98dabfd | |||
| e312cb0fe7 | |||
| 541d8dcd2e | |||
| c8742e2b96 | |||
| 711889bc3f | |||
| 70f8eeaca0 | |||
| 3339d0f47e | |||
| a38b5c4e01 | |||
| b20905da13 | |||
| 95e4dcfb53 | |||
| 841f19f49e | |||
| 2085ffb334 | |||
| b67351babe | |||
| aa5b8d0df7 | |||
| 7c949ad386 | |||
| 1646326164 | |||
| e44cb7e519 | |||
| d1e96fd0a8 | |||
| ff5d0c9886 | |||
| d7ec9ddcd2 | |||
| e7c6bd3730 | |||
| 9f5066aa6f | |||
| 3b24fedf92 | |||
| 8cd2a462b9 | |||
| 25b045d0a7 | |||
| 78acbdf0d9 | |||
| 617e2791ef | |||
| a0908ff55b | |||
| 2f1f8fb075 | |||
| 6f61b83ad5 | |||
| 8dfefb46d1 | |||
| 959442dc82 | |||
| c1aa152a63 | |||
| dd3d8a363a | |||
| bb509ec91d | |||
| 944f022060 | |||
| cd517a30af | |||
| 52d8655eec | |||
| f5786941e5 | |||
| 1f73941c43 | |||
| abeab51d00 | |||
| 9ef75f17bf | |||
| c3992bd77b | |||
| f79e66ac2e | |||
| 4eac212695 | |||
| b661882391 | |||
| 321488dcfe | |||
| 80caf2dab2 | |||
| c466465c32 | |||
| 404b65941a | |||
| 25f3adb47e | |||
| b9043b649d | |||
| 7b761fac78 | |||
| c47cdfa66f | |||
| 768a15c1dd | |||
| c1c3be1aa7 | |||
| b2e72c96f4 | |||
| f706219996 | |||
| 6152a661e8 | |||
| f1a5743f77 | |||
| 9f52fe4e4a | |||
| cb886a35c9 | |||
| 5707022951 | |||
| bd212cc000 | |||
| 6dd5666ed1 | |||
| f145663464 | |||
| 0d66475e10 | |||
| 22b143eba8 | |||
| 3fe13a72bd | |||
| d919905c96 | |||
| 96b1b76483 | |||
| f234df7a50 | |||
| e6d5c26df5 | |||
| ae59e7703e | |||
| e587b1d1b4 | |||
| 57271d7eaa | |||
| 8383552552 | |||
| aa8e12c464 | |||
| e7a05b6e74 | |||
| 2fd8f103ab | |||
| 33f76d1c2e | |||
| ea8f7ba1f9 | |||
| 0ffe5ac2fa | |||
| 7bac93aab3 | |||
| b61751e429 | |||
| 25dac32a88 | |||
| 56cf69a66b | |||
| 04d12bf2ba | |||
| 0b7957d612 | |||
| 61243f6c57 | |||
| 49ec3eb58e | |||
| fa6c1b69ce | |||
| 1e9a41dc94 | |||
| a30671751f | |||
| c246978dd7 | |||
| 72acda22b0 | |||
| f880950c3e | |||
| 1bed192de0 | |||
| 896a848001 | |||
| fb03787b73 | |||
| 83a949647f | |||
| 99a41bec4e | |||
| 312f5d3833 | |||
| 35bf0746a1 | |||
| 8d93b9e545 | |||
| b44d41d9ec | |||
| 4be9802d26 | |||
| 90b42799a2 | |||
| 71bbe93ab2 | |||
| 0535c1bbad | |||
| 4f6d598ea8 | |||
| 16e7dd83a3 | |||
| f23ba3b023 | |||
| 2aa2e750b9 | |||
| bcce296ca3 | |||
| c5ec955081 | |||
| 5d334922d7 | |||
| e9f9a4b750 | |||
| 1406453350 | |||
| 6d3c11627c | |||
| f800f7507c | |||
| 46a7993fb8 | |||
| 9f3dcf3733 | |||
| c849bccb83 | |||
| cffe424849 | |||
| 51938e9d1c | |||
| 19dec048b0 | |||
| bbb2909283 | |||
| aa7c7b7bd9 | |||
| c7bd5405c3 | |||
| dc981ce509 | |||
| c522569378 | |||
| 0db4b13e37 | |||
| 2709d38d63 | |||
| 71d1b70ab7 | |||
| 24676d4366 | |||
| e2f532709f | |||
| dc1ff80e10 | |||
| b5205cc3ca | |||
| a075cbedf5 | |||
| ef613a5db7 | |||
| 1571b8fd34 | |||
| a41b72578b | |||
| 9f3f9d05b8 | |||
| f9865a08bc | |||
| 13db2c13e5 | |||
| ce8ad3650a | |||
| b0b84c48ec | |||
| 5a4dcfb367 |
+40
-315
@@ -6,52 +6,6 @@ commands:
|
||||
- run:
|
||||
name: "Pull submodules"
|
||||
command: git submodule update --init
|
||||
apt-dependencies:
|
||||
parameters:
|
||||
llvm:
|
||||
type: string
|
||||
steps:
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
llvm-<<parameters.llvm>>-dev \
|
||||
clang-<<parameters.llvm>> \
|
||||
libclang-<<parameters.llvm>>-dev \
|
||||
lld-<<parameters.llvm>> \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
gcc-aarch64-linux-gnu \
|
||||
qemu-system-arm \
|
||||
qemu-user \
|
||||
gcc-avr \
|
||||
avr-libc
|
||||
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-8-dev
|
||||
install-node:
|
||||
steps:
|
||||
- run:
|
||||
name: "Install node.js"
|
||||
command: |
|
||||
wget https://nodejs.org/dist/v10.15.1/node-v10.15.1-linux-x64.tar.xz
|
||||
sudo tar -C /usr/local -xf node-v10.15.1-linux-x64.tar.xz
|
||||
sudo ln -s /usr/local/node-v10.15.1-linux-x64/bin/node /usr/bin/node
|
||||
rm node-v10.15.1-linux-x64.tar.xz
|
||||
install-chrome:
|
||||
steps:
|
||||
- run:
|
||||
name: "Install Chrome"
|
||||
command: |
|
||||
wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
|
||||
sudo apt install ./google-chrome-stable_current_amd64.deb
|
||||
install-wasmtime:
|
||||
steps:
|
||||
- run:
|
||||
name: "Install wasmtime"
|
||||
command: |
|
||||
curl https://wasmtime.dev/install.sh -sSf | bash
|
||||
sudo ln -s ~/.wasmtime/bin/wasmtime /usr/local/bin/wasmtime
|
||||
install-xtensa-toolchain:
|
||||
parameters:
|
||||
variant:
|
||||
@@ -68,26 +22,37 @@ commands:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-11-v1
|
||||
- llvm-source-13-v1
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-11-v1
|
||||
key: llvm-source-13-v1
|
||||
paths:
|
||||
- llvm-project
|
||||
build-wasi-libc:
|
||||
- llvm-project/clang/lib/Headers
|
||||
- llvm-project/clang/include
|
||||
- llvm-project/lld/include
|
||||
- llvm-project/llvm/include
|
||||
hack-ninja-jobs:
|
||||
steps:
|
||||
- run:
|
||||
name: "Hack Ninja to use less jobs"
|
||||
command: |
|
||||
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||
chmod +x /go/bin/ninja
|
||||
build-binaryen-linux:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- wasi-libc-sysroot-v4
|
||||
- binaryen-linux-v1
|
||||
- run:
|
||||
name: "Build wasi-libc"
|
||||
command: make wasi-libc
|
||||
name: "Build Binaryen"
|
||||
command: |
|
||||
make binaryen
|
||||
- save_cache:
|
||||
key: wasi-libc-sysroot-v4
|
||||
key: binaryen-linux-v1
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
- build/wasm-opt
|
||||
test-linux:
|
||||
parameters:
|
||||
llvm:
|
||||
@@ -95,11 +60,23 @@ commands:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- apt-dependencies:
|
||||
llvm: "<<parameters.llvm>>"
|
||||
- install-node
|
||||
- install-chrome
|
||||
- install-wasmtime
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
echo 'deb https://apt.llvm.org/buster/ llvm-toolchain-buster-<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends \
|
||||
llvm-<<parameters.llvm>>-dev \
|
||||
clang-<<parameters.llvm>> \
|
||||
libclang-<<parameters.llvm>>-dev \
|
||||
lld-<<parameters.llvm>> \
|
||||
gcc-avr \
|
||||
avr-libc \
|
||||
cmake \
|
||||
ninja-build
|
||||
- hack-ninja-jobs
|
||||
- build-binaryen-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
@@ -114,283 +91,31 @@ commands:
|
||||
key: wasi-libc-sysroot-systemclang-v3
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest XTENSA=0
|
||||
- run: make tinygo-test
|
||||
- run: make wasmtest
|
||||
- save_cache:
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- /go/pkg/mod
|
||||
- run: make fmt-check
|
||||
assert-test-linux:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
qemu-system-arm \
|
||||
qemu-user \
|
||||
gcc-avr \
|
||||
avr-libc
|
||||
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
|
||||
- install-node
|
||||
- install-wasmtime
|
||||
- install-xtensa-toolchain:
|
||||
variant: "linux-amd64"
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}
|
||||
- llvm-source-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-11-linux-v2-assert
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# install dependencies
|
||||
sudo apt-get install cmake ninja-build
|
||||
# hack ninja to use less jobs
|
||||
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||
chmod +x /go/bin/ninja
|
||||
# build!
|
||||
make ASSERT=1 llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-11-linux-v2-assert
|
||||
paths:
|
||||
llvm-build
|
||||
- run: make ASSERT=1
|
||||
- build-wasi-libc
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make ASSERT=1 test
|
||||
- save_cache:
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- /go/pkg/mod
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest TINYGO=build/tinygo
|
||||
build-linux:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
qemu-system-arm \
|
||||
qemu-user \
|
||||
gcc-avr \
|
||||
avr-libc
|
||||
sudo apt-get install --no-install-recommends libc6-dev-i386 lib32gcc-6-dev
|
||||
- install-node
|
||||
- install-wasmtime
|
||||
- install-xtensa-toolchain:
|
||||
variant: "linux-amd64"
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}
|
||||
- llvm-source-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-11-linux-v2-noassert
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# install dependencies
|
||||
sudo apt-get install cmake ninja-build
|
||||
# hack ninja to use less jobs
|
||||
echo -e '#!/bin/sh\n/usr/bin/ninja -j3 "$@"' > /go/bin/ninja
|
||||
chmod +x /go/bin/ninja
|
||||
# build!
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-11-linux-v2-noassert
|
||||
paths:
|
||||
llvm-build
|
||||
- build-wasi-libc
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make test
|
||||
- run:
|
||||
name: "Install fpm"
|
||||
command: |
|
||||
sudo apt-get install ruby ruby-dev
|
||||
sudo gem install --no-document fpm
|
||||
- run:
|
||||
name: "Build TinyGo release"
|
||||
command: |
|
||||
make release deb -j3
|
||||
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
|
||||
cp -p build/release.deb /tmp/tinygo_amd64.deb
|
||||
- store_artifacts:
|
||||
path: /tmp/tinygo.linux-amd64.tar.gz
|
||||
- store_artifacts:
|
||||
path: /tmp/tinygo_amd64.deb
|
||||
- save_cache:
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- /go/pkg/mod
|
||||
- run:
|
||||
name: "Extract release tarball"
|
||||
command: |
|
||||
mkdir -p ~/lib
|
||||
tar -C ~/lib -xf /tmp/tinygo.linux-amd64.tar.gz
|
||||
ln -s ~/lib/tinygo/bin/tinygo /go/bin/tinygo
|
||||
tinygo version
|
||||
- run: make smoketest
|
||||
build-macos:
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- run:
|
||||
name: "Install dependencies"
|
||||
command: |
|
||||
curl https://dl.google.com/go/go1.16.darwin-amd64.tar.gz -o go1.16.darwin-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.16.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/go/bin/go /usr/local/bin/go
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu
|
||||
- install-xtensa-toolchain:
|
||||
variant: "macos"
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-macos-v2-{{ checksum "go.mod" }}
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-11-macos-v1
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-11-macos-v1
|
||||
paths:
|
||||
- llvm-project
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-11-macos-v2
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# install dependencies
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
|
||||
# build!
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-11-macos-v2
|
||||
paths:
|
||||
llvm-build
|
||||
- restore_cache:
|
||||
keys:
|
||||
- wasi-libc-sysroot-macos-v3
|
||||
- run:
|
||||
name: "Build wasi-libc"
|
||||
command: make wasi-libc
|
||||
- save_cache:
|
||||
key: wasi-libc-sysroot-macos-v3
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
- run:
|
||||
name: "Test TinyGo"
|
||||
command: make test
|
||||
- run:
|
||||
name: "Build TinyGo release"
|
||||
command: |
|
||||
make release -j3
|
||||
cp -p build/release.tar.gz /tmp/tinygo.darwin-amd64.tar.gz
|
||||
- store_artifacts:
|
||||
path: /tmp/tinygo.darwin-amd64.tar.gz
|
||||
- run:
|
||||
name: "Extract release tarball"
|
||||
command: |
|
||||
mkdir -p ~/lib
|
||||
tar -C /usr/local/opt -xf /tmp/tinygo.darwin-amd64.tar.gz
|
||||
ln -s /usr/local/opt/tinygo/bin/tinygo /usr/local/bin/tinygo
|
||||
tinygo version
|
||||
- run: make smoketest AVR=0
|
||||
- save_cache:
|
||||
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- /go/pkg/mod
|
||||
|
||||
jobs:
|
||||
test-llvm10-go113:
|
||||
docker:
|
||||
- image: circleci/golang:1.13-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "10"
|
||||
test-llvm10-go114:
|
||||
docker:
|
||||
- image: circleci/golang:1.14-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "10"
|
||||
test-llvm11-go115:
|
||||
docker:
|
||||
- image: circleci/golang:1.15-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "11"
|
||||
test-llvm11-go116:
|
||||
test-llvm12-go116:
|
||||
docker:
|
||||
- image: circleci/golang:1.16-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "11"
|
||||
assert-test-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.14-stretch
|
||||
steps:
|
||||
- assert-test-linux
|
||||
build-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.14-stretch
|
||||
steps:
|
||||
- build-linux
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: "10.1.0"
|
||||
steps:
|
||||
- build-macos
|
||||
|
||||
|
||||
llvm: "12"
|
||||
|
||||
workflows:
|
||||
test-all:
|
||||
jobs:
|
||||
- test-llvm10-go113
|
||||
- test-llvm10-go114
|
||||
- test-llvm11-go115
|
||||
- test-llvm11-go116
|
||||
- build-linux
|
||||
- build-macos
|
||||
- assert-test-linux
|
||||
- test-llvm12-go116
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
build/
|
||||
llvm-*/
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
name: macOS
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- release
|
||||
|
||||
jobs:
|
||||
build-macos:
|
||||
name: build-macos
|
||||
runs-on: macos-10.15
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.17'
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install qemu binaryen
|
||||
- name: Install Xtensa toolchain
|
||||
shell: bash
|
||||
run: |
|
||||
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.tar.gz
|
||||
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.tar.gz
|
||||
sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld
|
||||
rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-macos.tar.gz
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache LLVM source
|
||||
uses: actions/cache@v2
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-13-macos-v1
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
llvm-project/lld/include
|
||||
llvm-project/llvm/include
|
||||
- name: Download LLVM source
|
||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||
run: make llvm-source
|
||||
- name: Cache LLVM build
|
||||
uses: actions/cache@v2
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-13-macos-v2
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
# fetch LLVM source
|
||||
rm -rf llvm-project
|
||||
make llvm-source
|
||||
# install dependencies
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake ninja
|
||||
# build!
|
||||
make llvm-build
|
||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||
- name: Cache wasi-libc sysroot
|
||||
uses: actions/cache@v2
|
||||
id: cache-wasi-libc
|
||||
with:
|
||||
key: wasi-libc-sysroot-v1
|
||||
path: lib/wasi-libc/sysroot
|
||||
- name: Build wasi-libc
|
||||
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
|
||||
run: make wasi-libc
|
||||
- name: Test TinyGo
|
||||
shell: bash
|
||||
run: make test GOTESTFLAGS="-v -short"
|
||||
- name: Build TinyGo release tarball
|
||||
run: make release -j3
|
||||
- name: Make release artifact
|
||||
shell: bash
|
||||
run: cp -p build/release.tar.gz build/tinygo.darwin-amd64.tar.gz
|
||||
- name: Publish release artifact
|
||||
# Note: this release artifact is double-zipped, see:
|
||||
# https://github.com/actions/upload-artifact/issues/39
|
||||
# We can essentially pick one of these:
|
||||
# - have a double-zipped artifact when downloaded from the UI
|
||||
# - have a very slow artifact upload
|
||||
# We're doing the former here, to keep artifact uploads fast.
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-double-zipped
|
||||
path: build/tinygo.darwin-amd64.tar.gz
|
||||
- name: Smoke tests
|
||||
shell: bash
|
||||
run: make smoketest TINYGO=$(PWD)/build/tinygo AVR=0
|
||||
@@ -0,0 +1,59 @@
|
||||
# This is the Github action to build and push the tinygo/tinygo-dev Docker image.
|
||||
# If you are looking for the tinygo/tinygo "release" Docker image please see
|
||||
# https://github.com/tinygo-org/docker
|
||||
#
|
||||
name: Docker
|
||||
on:
|
||||
push:
|
||||
branches: [ dev, fix-docker-llvm-build ]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: build-push-dev
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v3
|
||||
with:
|
||||
images: |
|
||||
tinygo/tinygo-dev
|
||||
ghcr.io/${{ github.repository }}/tinygo-dev
|
||||
tags: |
|
||||
type=sha,format=long
|
||||
type=raw,value=latest
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
- name: Log in to Github Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Trigger Drivers repo build on CircleCI
|
||||
run: |
|
||||
curl --location --request POST 'https://circleci.com/api/v2/project/github/tinygo-org/drivers/pipeline' \
|
||||
--header 'Content-Type: application/json' \
|
||||
-d '{"branch": "dev"}' \
|
||||
-u "${{ secrets.CIRCLECI_API_TOKEN }}"
|
||||
@@ -0,0 +1,241 @@
|
||||
name: Linux
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- release
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
# Build Linux binaries, ready for release.
|
||||
# This intentionally uses an older Linux image, so that we compile against
|
||||
# an older glibc version and therefore are compatible with a wide range of
|
||||
# Linux distributions.
|
||||
runs-on: ubuntu-18.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install apt dependencies
|
||||
run: |
|
||||
sudo apt-get install --no-install-recommends \
|
||||
ninja-build
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.17'
|
||||
- name: Cache Go
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
key: go-cache-linux-v1-${{ hashFiles('go.mod') }}
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
- name: Cache LLVM source
|
||||
uses: actions/cache@v2
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-13-linux-v1
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
llvm-project/lld/include
|
||||
llvm-project/llvm/include
|
||||
- name: Download LLVM source
|
||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||
run: make llvm-source
|
||||
- name: Cache LLVM build
|
||||
uses: actions/cache@v2
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-13-linux-v1
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
# fetch LLVM source
|
||||
rm -rf llvm-project
|
||||
make llvm-source
|
||||
# build!
|
||||
make llvm-build
|
||||
# Remove unnecessary object files (to reduce cache size).
|
||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||
- name: Cache Binaryen
|
||||
uses: actions/cache@v2
|
||||
id: cache-binaryen
|
||||
with:
|
||||
key: binaryen-linux-v1
|
||||
path: build/wasm-opt
|
||||
- name: Build Binaryen
|
||||
if: steps.cache-binaryen.outputs.cache-hit != 'true'
|
||||
run: make binaryen
|
||||
- name: Cache wasi-libc
|
||||
uses: actions/cache@v2
|
||||
id: cache-wasi-libc
|
||||
with:
|
||||
key: wasi-libc-sysroot-linux-asserts-v2
|
||||
path: lib/wasi-libc/sysroot
|
||||
- name: Build wasi-libc
|
||||
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
|
||||
run: make wasi-libc
|
||||
- name: Install fpm
|
||||
run: |
|
||||
sudo apt-get install ruby ruby-dev
|
||||
sudo gem install --no-document fpm
|
||||
- name: Build TinyGo release
|
||||
run: |
|
||||
make release deb -j3
|
||||
cp -p build/release.tar.gz /tmp/tinygo.linux-amd64.tar.gz
|
||||
cp -p build/release.deb /tmp/tinygo_amd64.deb
|
||||
- name: Publish release artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-double-zipped
|
||||
path: |
|
||||
/tmp/tinygo.linux-amd64.tar.gz
|
||||
/tmp/tinygo_amd64.deb
|
||||
test-linux-build:
|
||||
# Test the binaries built in the build-linux job by running the smoke tests.
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-linux
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.17'
|
||||
- name: Install wasmtime
|
||||
run: |
|
||||
curl https://wasmtime.dev/install.sh -sSf | bash
|
||||
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
|
||||
- name: Download release artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: release-double-zipped
|
||||
- name: Extract release tarball
|
||||
run: |
|
||||
mkdir -p ~/lib
|
||||
tar -C ~/lib -xf tinygo.linux-amd64.tar.gz
|
||||
ln -s ~/lib/tinygo/bin/tinygo ~/go/bin/tinygo
|
||||
- name: Install apt dependencies
|
||||
run: |
|
||||
sudo apt-get install --no-install-recommends \
|
||||
gcc-avr \
|
||||
avr-libc
|
||||
- name: "Install Xtensa toolchain"
|
||||
run: |
|
||||
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
|
||||
sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld
|
||||
rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
|
||||
- run: make tinygo-test-wasi-fast
|
||||
- run: make smoketest
|
||||
assert-test-linux:
|
||||
# Run all tests that can run on Linux, with LLVM assertions enabled to catch
|
||||
# potential bugs.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install apt dependencies
|
||||
run: |
|
||||
echo "Show cpuinfo; sometimes useful when troubleshooting"
|
||||
cat /proc/cpuinfo
|
||||
sudo apt-get update
|
||||
sudo apt-get install --no-install-recommends \
|
||||
qemu-system-arm \
|
||||
qemu-system-riscv32 \
|
||||
qemu-user \
|
||||
gcc-avr \
|
||||
avr-libc \
|
||||
simavr \
|
||||
ninja-build
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.17'
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '12'
|
||||
- name: Install wasmtime
|
||||
run: |
|
||||
curl https://wasmtime.dev/install.sh -sSf | bash
|
||||
echo "$HOME/.wasmtime/bin" >> $GITHUB_PATH
|
||||
- name: Cache Go
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
key: go-cache-linux-asserts-v1-${{ hashFiles('go.mod') }}
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
- name: Cache LLVM source
|
||||
uses: actions/cache@v2
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-13-linux-asserts-v1
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
llvm-project/lld/include
|
||||
llvm-project/llvm/include
|
||||
- name: Download LLVM source
|
||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||
run: make llvm-source
|
||||
- name: Cache LLVM build
|
||||
uses: actions/cache@v2
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-13-linux-asserts-v1
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
# fetch LLVM source
|
||||
rm -rf llvm-project
|
||||
make llvm-source
|
||||
# build!
|
||||
make llvm-build ASSERT=1
|
||||
# Remove unnecessary object files (to reduce cache size).
|
||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||
- name: Cache Binaryen
|
||||
uses: actions/cache@v2
|
||||
id: cache-binaryen
|
||||
with:
|
||||
key: binaryen-linux-asserts-v1
|
||||
path: build/wasm-opt
|
||||
- name: Build Binaryen
|
||||
if: steps.cache-binaryen.outputs.cache-hit != 'true'
|
||||
run: make binaryen
|
||||
- name: Cache wasi-libc
|
||||
uses: actions/cache@v2
|
||||
id: cache-wasi-libc
|
||||
with:
|
||||
key: wasi-libc-sysroot-linux-asserts-v2
|
||||
path: lib/wasi-libc/sysroot
|
||||
- name: Build wasi-libc
|
||||
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
|
||||
run: make wasi-libc
|
||||
- run: make gen-device -j4
|
||||
- name: Test TinyGo
|
||||
run: make ASSERT=1 test
|
||||
- name: Build TinyGo
|
||||
run: |
|
||||
make ASSERT=1
|
||||
echo "$(pwd)/build" >> $GITHUB_PATH
|
||||
- name: Test stdlib packages
|
||||
run: make tinygo-test
|
||||
- name: Install Xtensa toolchain
|
||||
run: |
|
||||
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
|
||||
sudo ln -s /usr/local/xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/xtensa-esp32-elf-ld
|
||||
rm xtensa-esp32-elf-gcc8_2_0-esp-2020r2-linux-amd64.tar.gz
|
||||
- run: make smoketest
|
||||
- run: make wasmtest
|
||||
@@ -0,0 +1,97 @@
|
||||
name: Windows
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- release
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
runs-on: windows-2019
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: '1.17'
|
||||
- uses: MinoruSekine/setup-scoop@v1
|
||||
- name: Install Dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
scoop install ninja binaryen
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: true
|
||||
- name: Cache Go
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
key: go-cache-windows-v1-${{ hashFiles('go.mod') }}
|
||||
path: |
|
||||
~/AppData/Local/go-build
|
||||
~/go/pkg/mod
|
||||
- name: Cache LLVM source
|
||||
uses: actions/cache@v2
|
||||
id: cache-llvm-source
|
||||
with:
|
||||
key: llvm-source-13-windows-v1
|
||||
path: |
|
||||
llvm-project/clang/lib/Headers
|
||||
llvm-project/clang/include
|
||||
llvm-project/lld/include
|
||||
llvm-project/llvm/include
|
||||
- name: Download LLVM source
|
||||
if: steps.cache-llvm-source.outputs.cache-hit != 'true'
|
||||
run: make llvm-source
|
||||
- name: Cache LLVM build
|
||||
uses: actions/cache@v2
|
||||
id: cache-llvm-build
|
||||
with:
|
||||
key: llvm-build-13-windows-v1
|
||||
path: llvm-build
|
||||
- name: Build LLVM
|
||||
if: steps.cache-llvm-build.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
# fetch LLVM source
|
||||
rm -rf llvm-project
|
||||
make llvm-source
|
||||
# build!
|
||||
make llvm-build
|
||||
# Remove unnecessary object files (to reduce cache size).
|
||||
find llvm-build -name CMakeFiles -prune -exec rm -r '{}' \;
|
||||
- name: Cache wasi-libc sysroot
|
||||
uses: actions/cache@v2
|
||||
id: cache-wasi-libc
|
||||
with:
|
||||
key: wasi-libc-sysroot-v1
|
||||
path: lib/wasi-libc/sysroot
|
||||
- name: Build wasi-libc
|
||||
if: steps.cache-wasi-libc.outputs.cache-hit != 'true'
|
||||
run: make wasi-libc
|
||||
- name: Test TinyGo
|
||||
shell: bash
|
||||
run: make test GOTESTFLAGS="-v -short"
|
||||
- name: Build TinyGo release tarball
|
||||
run: make build/release -j4
|
||||
- name: Make release artifact
|
||||
shell: bash
|
||||
working-directory: build/release
|
||||
run: 7z -tzip a release.zip tinygo
|
||||
- name: Publish release artifact
|
||||
# Note: this release artifact is double-zipped, see:
|
||||
# https://github.com/actions/upload-artifact/issues/39
|
||||
# We can essentially pick one of these:
|
||||
# - have a dobule-zipped artifact when downloaded from the UI
|
||||
# - have a very slow artifact upload
|
||||
# We're doing the former here, to keep artifact uploads fast.
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: release-double-zipped
|
||||
path: build/release/release.zip
|
||||
- name: Smoke tests
|
||||
shell: bash
|
||||
run: make smoketest TINYGO=$(PWD)/build/tinygo AVR=0 XTENSA=0
|
||||
- name: Test stdlib packages
|
||||
run: make tinygo-test
|
||||
+13
-1
@@ -1,4 +1,3 @@
|
||||
build
|
||||
docs/_build
|
||||
src/device/avr/*.go
|
||||
src/device/avr/*.ld
|
||||
@@ -16,6 +15,19 @@ src/device/stm32/*.go
|
||||
src/device/stm32/*.s
|
||||
src/device/kendryte/*.go
|
||||
src/device/kendryte/*.s
|
||||
src/device/rp/*.go
|
||||
src/device/rp/*.s
|
||||
vendor
|
||||
llvm-build
|
||||
llvm-project
|
||||
build/*
|
||||
|
||||
# Ignore files generated by smoketest
|
||||
test.bin
|
||||
test.elf
|
||||
test.exe
|
||||
test.gba
|
||||
test.hex
|
||||
test.nro
|
||||
test.wasm
|
||||
wasm.wasm
|
||||
@@ -23,3 +23,12 @@
|
||||
[submodule "lib/stm32-svd"]
|
||||
path = lib/stm32-svd
|
||||
url = https://github.com/tinygo-org/stm32-svd
|
||||
[submodule "lib/musl"]
|
||||
path = lib/musl
|
||||
url = git://git.musl-libc.org/musl
|
||||
[submodule "lib/binaryen"]
|
||||
path = lib/binaryen
|
||||
url = https://github.com/WebAssembly/binaryen.git
|
||||
[submodule "lib/mingw-w64"]
|
||||
path = lib/mingw-w64
|
||||
url = https://github.com/mingw-w64/mingw-w64.git
|
||||
|
||||
+2
-7
@@ -11,19 +11,14 @@ lld so that the binary can be easily moved between systems. It also shows how to
|
||||
build a release tarball that includes this binary and all necessary extra files.
|
||||
|
||||
**Note**: this documentation describes how to build a statically linked release
|
||||
tarball. If you want to develop TinyGo, you will probably want to follow a
|
||||
different guide:
|
||||
|
||||
* [Linux](https://tinygo.org/getting-started/linux/#source-install)
|
||||
* [macOS](https://tinygo.org/getting-started/macos/#source-install)
|
||||
* [Windows](https://tinygo.org/getting-started/windows/#source-install)
|
||||
tarball. If you want to help with development of TinyGo itself, you should follow the guide located at https://tinygo.org/docs/guides/build/
|
||||
|
||||
## Dependencies
|
||||
|
||||
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
||||
build tools to be built. Go is of course necessary to build TinyGo itself.
|
||||
|
||||
* Go (1.13+)
|
||||
* Go (1.15+)
|
||||
* Standard build tools (gcc/clang)
|
||||
* git
|
||||
* CMake
|
||||
|
||||
+457
@@ -1,3 +1,460 @@
|
||||
0.22.0
|
||||
---
|
||||
|
||||
* **command line**
|
||||
- add asyncify to scheduler flag help
|
||||
- support -run for tests
|
||||
- remove FreeBSD target support
|
||||
- add LLVM 12 and LLVM 13 support, use LLVM 13 by default
|
||||
- add support for ARM64 MacOS
|
||||
- improve help
|
||||
- check /run/media as well as /media on Linux for non-debian-based distros
|
||||
- `test`: set cmd.Dir even when running emulators
|
||||
- `info`: add JSON output using the `-json` flag
|
||||
* **compiler**
|
||||
- `builder`: fix off-by-one in size calculation
|
||||
- `builder`: handle concurrent library header rename
|
||||
- `builder`: use flock to avoid double-compiles
|
||||
- `builder`: use build ID as cache key
|
||||
- `builder`: add -fno-stack-protector to musl build
|
||||
- `builder`: update clang header search path to look in /usr/lib
|
||||
- `builder`: explicitly disable unwind tables for ARM
|
||||
- `cgo`: add support for `C.CString` and related functions
|
||||
- `compiler`: fix ranging over maps with particular map types
|
||||
- `compiler`: add correct debug location to init instructions
|
||||
- `compiler`: fix emission of large object layouts
|
||||
- `compiler`: work around AVR atomics bugs
|
||||
- `compiler`: predeclare runtime.trackPointer
|
||||
- `interp`: work around AVR function pointers in globals
|
||||
- `interp`: run goroutine starts and checks at runtime
|
||||
- `interp`: always run atomic and volatile loads/stores at runtime
|
||||
- `interp`: bump timeout to 180 seconds
|
||||
- `interp`: handle type assertions on nil interfaces
|
||||
- `loader`: elminate goroot cache inconsistency
|
||||
- `loader`: respect $GOROOT when running `go list`
|
||||
- `transform`: allocate the correct amount of bytes in an alloca
|
||||
- `transform`: remove switched func lowering
|
||||
* **standard library**
|
||||
- `crypto/rand`: show error if platform has no rng
|
||||
- `device/*`: add `*_Msk` field for each bit field and avoid duplicates
|
||||
- `device/*`: provide Set/Get for each register field described in the SVD files
|
||||
- `internal/task`: swap stack chain when switching goroutines
|
||||
- `internal/task`: remove `-scheduler=coroutines`
|
||||
- `machine`: add `Device` string constant
|
||||
- `net`: add bare Interface implementation
|
||||
- `net`: add net.Buffers
|
||||
- `os`: stub out support for some features
|
||||
- `os`: obey TMPDIR on unix, TMP on Windows, etc
|
||||
- `os`: implement `ReadAt`, `Mkdir`, `Remove`, `Stat`, `Lstat`, `CreateTemp`, `MkdirAll`, `Chdir`, `Chmod`, `Clearenv`, `Unsetenv`, `Setenv`, `MkdirTemp`, `Rename`, `Seek`, `ExpandEnv`, `Symlink`, `Readlink`
|
||||
- `os`: implement `File.Stat`
|
||||
- `os`: fix `IsNotExist` on nonexistent path
|
||||
- `os`: fix opening files on WASI in read-only mode
|
||||
- `os`: work around lack of `syscall.seek` on 386 and arm
|
||||
- `reflect`: make sure indirect pointers are handled correctly
|
||||
- `runtime`: allow comparing interfaces
|
||||
- `runtime`: use LLVM intrinsic to read the stack pointer
|
||||
- `runtime`: strengthen hashmap hash function for structs and arrays
|
||||
- `runtime`: fix float/complex hashing
|
||||
- `runtime`: fix nil map dereference
|
||||
- `runtime`: add realloc implementation to GCs
|
||||
- `runtime`: handle negative sleep times
|
||||
- `runtime`: correct GC scan bounds
|
||||
- `runtime`: remove extalloc GC
|
||||
- `rumtime`: implement `__sync` libcalls as critical sections for most microcontrollers
|
||||
- `runtime`: add stubs for `Func.FileLine` and `Frame.PC`
|
||||
- `sync`: fix concurrent read-lock on write-locked RWMutex
|
||||
- `sync`: add a package doc
|
||||
- `sync`: add tests
|
||||
- `syscall`: add support for `Mmap` and `Mprotect`
|
||||
- `syscall`: fix array size for mmap slice creation
|
||||
- `syscall`: enable `Getwd` in wasi
|
||||
- `testing`: add a stub for `CoverMode`
|
||||
- `testing`: support -bench option to run benchmarks matching the given pattern.
|
||||
- `testing`: support b.SetBytes(); implement sub-benchmarks.
|
||||
- `testing`: replace spaces with underscores in test/benchmark names, as upstream does
|
||||
- `testing`: implement testing.Cleanup
|
||||
- `testing`: allow filtering subbenchmarks with the `-bench` flag
|
||||
- `testing`: implement `-benchtime` flag
|
||||
- `testing`: print duration
|
||||
- `testing`: allow filtering of subtests using `-run`
|
||||
* **targets**
|
||||
- `all`: change LLVM features to match vanilla Clang
|
||||
- `avr`: use interrupt-based timer which is much more accurate
|
||||
- `nrf`: fix races in I2C
|
||||
- `samd51`: implement TRNG for randomness
|
||||
- `stm32`: pull-up on I2C lines
|
||||
- `stm32`: fix timeout for i2c comms
|
||||
- `stm32f4`, `stm32f103`: initial implementation for ADC
|
||||
- `stm32f4`, `stm32f7`, `stm32l0x2`, `stm32l4`, `stm32l5`, `stm32wl`: TRNG implementation in crypto/rand
|
||||
- `stm32wl`: add I2C support
|
||||
- `windows`: add support for the `-size=` flag
|
||||
- `wasm`: add support for `tinygo test`
|
||||
- `wasi`, `wasm`: raise default stack size to 16 KiB
|
||||
* **boards**
|
||||
- add M5Stack
|
||||
- add lorae5 (stm32wle) support
|
||||
- add Generic Node Sensor Edition
|
||||
- add STM32F469 Discovery
|
||||
- add M5Stamp C3
|
||||
- add Blues Wireless Swan
|
||||
- `bluepill`: add definitions for ADC pins
|
||||
- `stm32f4disco`: add definitions for ADC pins
|
||||
- `stm32l552ze`: use supported stlink interface
|
||||
- `microbit-v2`: add some pin definitions
|
||||
|
||||
|
||||
0.21.0
|
||||
---
|
||||
|
||||
* **command line**
|
||||
- drop support for LLVM 10
|
||||
- `build`: drop support for LLVM targets in the -target flag
|
||||
- `build`: fix paths in error messages on Windows
|
||||
- `build`: add -p flag to set parallelism
|
||||
- `lldb`: implement `tinygo lldb` subcommand
|
||||
- `test`: use emulator exit code instead of parsing test output
|
||||
- `test`: pass testing arguments to wasmtime
|
||||
* **compiler**
|
||||
- use -opt flag for optimization level in CFlags (-Os, etc)
|
||||
- `builder`: improve accuracy of the -size=full flag
|
||||
- `builder`: hardcode some more frame sizes for __aeabi_* functions
|
||||
- `builder`: add support for -size= flag for WebAssembly
|
||||
- `cgo`: fix line/column reporting in syntax error messages
|
||||
- `cgo`: support function definitions in CGo headers
|
||||
- `cgo`: implement rudimentary C array decaying
|
||||
- `cgo`: add support for stdio in picolibc and wasi-libc
|
||||
- `cgo`: run CGo parser per file, not per CGo fragment
|
||||
- `compiler`: fix unintentionally exported math functions
|
||||
- `compiler`: properly implement div and rem operations
|
||||
- `compiler`: add support for recursive function types
|
||||
- `compiler`: add support for the `go` keyword on interface methods
|
||||
- `compiler`: add minsize attribute for -Oz
|
||||
- `compiler`: add "target-cpu" and "target-features" attributes
|
||||
- `compiler`: fix indices into strings and arrays
|
||||
- `compiler`: fix string compare functions
|
||||
- `interp`: simplify some code to avoid some errors
|
||||
- `interp`: support recursive globals (like linked lists) in globals
|
||||
- `interp`: support constant globals
|
||||
- `interp`: fix reverting of extractvalue/insertvalue with multiple indices
|
||||
- `transform`: work around renamed return type after merging LLVM modules
|
||||
* **standard library**
|
||||
- `internal/bytealg`: fix indexing error in Compare()
|
||||
- `machine`: support Pin.Get() function when the pin is configured as output
|
||||
- `net`, `syscall`: Reduce code duplication by switching to internal/itoa.
|
||||
- `os`: don't try to read executable path on baremetal
|
||||
- `os`: implement Getwd
|
||||
- `os`: add File.WriteString and File.WriteAt
|
||||
- `reflect`: fix type.Size() to account for struct padding
|
||||
- `reflect`: don't construct an interface-in-interface value
|
||||
- `reflect`: implement Value.Elem() for interface values
|
||||
- `reflect`: fix Value.Index() in a specific case
|
||||
- `reflect`: add support for DeepEqual
|
||||
- `runtime`: add another set of invalid unicode runes to encodeUTF8()
|
||||
- `runtime`: only initialize os.runtime_args when needed
|
||||
- `runtime`: only use CRLF on baremetal systems for println
|
||||
- `runtime/debug`: stub `debug.SetMaxStack`
|
||||
- `runtime/debug`: stub `debug.Stack`
|
||||
- `testing`: add a stub for t.Parallel()
|
||||
- `testing`: add support for -test.short flag
|
||||
- `testing`: stub B.ReportAllocs()
|
||||
- `testing`: add `testing.Verbose`
|
||||
- `testing`: stub `testing.AllocsPerRun`
|
||||
* **targets**
|
||||
- fix gen-device-svd to handle 64-bit values
|
||||
- add CPU and Features property to all targets
|
||||
- match LLVM triple to the one Clang uses
|
||||
- `atsam`: simplify definition of SERCOM UART, I2C and SPI peripherals
|
||||
- `atsam`: move I2S0 to machine file
|
||||
- `esp32`: fix SPI configuration
|
||||
- `esp32c3`: add support for GDB debugging
|
||||
- `esp32c3`: add support for CPU interrupts
|
||||
- `esp32c3`: use tasks scheduler by default
|
||||
- `fe310`: increase CPU frequency from 16MHz to 320MHz
|
||||
- `fe310`: add support for bit banging drivers
|
||||
- `linux`: build static binaries using musl
|
||||
- `linux`: reduce binary size by calling `write` instead of `putchar`
|
||||
- `linux`: add support for GOARM
|
||||
- `riscv`: implement 32-bit atomic operations
|
||||
- `riscv`: align the heap to 16 bytes
|
||||
- `riscv`: switch to tasks-based scheduler
|
||||
- `rp2040`: add CPUFrequency()
|
||||
- `rp2040`: improve I2C baud rate configuration
|
||||
- `rp2040`: add pin interrupt API
|
||||
- `rp2040`: refactor PWM code and fix Period calculation
|
||||
- `stm32f103`: fix SPI
|
||||
- `stm32f103`: make SPI frequency selection more flexible
|
||||
- `qemu`: signal correct exit code to QEMU
|
||||
- `wasi`: run C/C++ constructors at startup
|
||||
- `wasm`: ensure heapptr is aligned
|
||||
- `wasm`: update wasi-libc dependency
|
||||
- `wasm`: wasi: use asyncify
|
||||
- `wasm`: support `-scheduler=none`
|
||||
- `windows`: add support for Windows (amd64 only for now)
|
||||
* **boards**
|
||||
- `feather-stm32f405`, `feather-rp2040`: add I2C pin names
|
||||
- `m5stack-core2`: add M5Stack Core2
|
||||
- `nano-33-ble`: SoftDevice s140v7 support
|
||||
- `nano-33-ble`: add constants for more on-board pins
|
||||
|
||||
|
||||
0.20.0
|
||||
---
|
||||
|
||||
* **command line**
|
||||
- add support for Go 1.17
|
||||
- improve Go version detection
|
||||
- add support for the Black Magic Probe (BMP)
|
||||
- add a flag for creating cpu profiles
|
||||
* **compiler**
|
||||
- `builder:` list libraries at the end of the linker command
|
||||
- `builder:` strip debug information at link time instead of at compile time
|
||||
- `builder:` add missing error check for `ioutil.TempFile()`
|
||||
- `builder:` simplify running of jobs
|
||||
- `compiler:` move LLVM math builtin support into the compiler
|
||||
- `compiler:` move math aliases from the runtime to the compiler
|
||||
- `compiler:` add aliases for many hashing packages
|
||||
- `compiler:` add `*ssa.MakeSlice` bounds tests
|
||||
- `compiler:` fix max possible slice
|
||||
- `compiler:` add support for new language features of Go 1.17
|
||||
- `compiler:` fix equally named structs in different scopes
|
||||
- `compiler:` avoid zero-sized alloca in channel operations
|
||||
- `interp:` don't ignore array indices for untyped objects
|
||||
- `interp:` keep reverted package initializers in order
|
||||
- `interp:` fix bug in compiler-time/run-time package initializers
|
||||
- `loader:` fix panic in CGo files with syntax errors
|
||||
- `transform:` improve GC stack slot pass to work around a bug
|
||||
* **standard library**
|
||||
- `crypto/rand`: switch to `arc4random_buf`
|
||||
- `math:` fix `math.Max` and `math.Min`
|
||||
- `math/big`: fix undefined symbols error
|
||||
- `net:` add MAC address implementation
|
||||
- `os:` implement `os.Executable`
|
||||
- `os:` add `SEEK_SET`, `SEEK_CUR`, and `SEEK_END`
|
||||
- `reflect:` add StructField.IsExported method
|
||||
- `runtime:` reset heapptr to heapStart after preinit()
|
||||
- `runtime:` add `subsections_via_symbols` to assembly files on darwin
|
||||
- `testing:` add subset implementation of Benchmark
|
||||
- `testing:` test testing package using `tinygo test`
|
||||
- `testing:` add support for the `-test.v` flag
|
||||
* **targets**
|
||||
- `386:` bump minimum requirement to the Pentium 4
|
||||
- `arm:` switch to Thumb instruction set on ARM
|
||||
- `atsamd:` fix copy-paste error for atsamd21/51 calibTrim block
|
||||
- `baremetal`,`wasm`: support command line params and environment variables
|
||||
- `cortexm:` fix stack overflow because of unaligned stacks
|
||||
- `esp32c3:` add support for the ESP32-C3 from Espressif
|
||||
- `nrf52840:` fix ram size
|
||||
- `nxpmk66f18:` fix a suspicious bitwise operation
|
||||
- `rp2040:` add SPI support
|
||||
- `rp2040:` add I2C support
|
||||
- `rp2040:` add PWM implementation
|
||||
- `rp2040:` add openocd configuration
|
||||
- `stm32:` add support for PortMask* functions for WS2812 support
|
||||
- `unix:` fix time base for time.Now()
|
||||
- `unix:` check for mmap error and act accordingly
|
||||
- `wasm:` override dlmalloc heap implementation from wasi-libc
|
||||
- `wasm:` align heap to 16 bytes
|
||||
- `wasm:` add support for the crypto/rand package
|
||||
* **boards**
|
||||
- add `DefaultUART` to adafruit boards
|
||||
- `arduino-mkrwifi1010:` add board definition for Arduino MKR WiFi 1010
|
||||
- `arduino-mkrwifi1010:` fix pin definition of `NINA_RESETN`
|
||||
- `feather-nrf52:` fix pin definition of uart
|
||||
- `feather-rp2040:` add pin name definition
|
||||
- `gameboy-advance:` fix ROM header
|
||||
- `mdbt50qrx-uf2:` add Raytac MDBT50Q-RX Dongle with TinyUF2
|
||||
- `nano-rp2040:` define `NINA_SPI` and fix wifinina pins
|
||||
- `teensy40:` enable hardware UART reconfiguration, fix receive watermark interrupt
|
||||
|
||||
|
||||
0.19.0
|
||||
---
|
||||
|
||||
* **command line**
|
||||
- don't consider compile-only tests as failing
|
||||
- add -test flag for `tinygo list`
|
||||
- escape commands while printing them with the -x flag
|
||||
- make flash-command portable and safer to use
|
||||
- use `extended-remote` instead of `remote` in GDB
|
||||
- detect specific serial port IDs based on USB vid/pid
|
||||
- add a flag to the command line to select the serial implementation
|
||||
* **compiler**
|
||||
- `cgo`: improve constant parser
|
||||
- `compiler`: support chained interrupt handlers
|
||||
- `compiler`: add support for running a builtin in a goroutine
|
||||
- `compiler`: do not emit nil checks for loading closure variables
|
||||
- `compiler`: skip context parameter when starting regular goroutine
|
||||
- `compiler`: refactor method names
|
||||
- `compiler`: add function and global section pragmas
|
||||
- `compiler`: implement `syscall.rawSyscallNoError` in inline assembly
|
||||
- `interp`: ignore inline assembly in markExternal
|
||||
- `interp`: fix a bug in pointer cast workaround
|
||||
- `loader`: fix testing a main package
|
||||
* **standard library**
|
||||
- `crypto/rand`: replace this package with a TinyGo version
|
||||
- `machine`: make USBCDC global a pointer
|
||||
- `machine`: make UART objects pointer receivers
|
||||
- `machine`: define Serial as the default output
|
||||
- `net`: add initial support for net.IP
|
||||
- `net`: add more net compatibility
|
||||
- `os`: add stub for os.ReadDir
|
||||
- `os`: add FileMode constants from Go 1.16
|
||||
- `os`: add stubs required for net/http
|
||||
- `os`: implement process related functions
|
||||
- `reflect`: implement AppendSlice
|
||||
- `reflect`: add stubs required for net/http
|
||||
- `runtime`: make task.Data a 64-bit integer to avoid overflow
|
||||
- `runtime`: expose memory stats
|
||||
- `sync`: implement NewCond
|
||||
- `syscall`: fix int type in libc version
|
||||
* **targets**
|
||||
- `cortexm`: do not disable interrupts on abort
|
||||
- `cortexm`: bump default stack size to 2048 bytes
|
||||
- `nrf`: avoid heap allocation in waitForEvent
|
||||
- `nrf`: don't trigger a heap allocation in SPI.Transfer
|
||||
- `nrf52840`: add support for flashing with the BOSSA tool
|
||||
- `rp2040`: add support for GPIO input
|
||||
- `rp2040`: add basic support for ADC
|
||||
- `rp2040`: gpio and adc pin definitions
|
||||
- `rp2040`: implement UART
|
||||
- `rp2040`: patch elf to checksum 2nd stage boot
|
||||
- `stm32`: add PWM for most chips
|
||||
- `stm32`: add support for pin interrupts
|
||||
- `stm32f103`: add support for PinInputPullup / PinInputPulldown
|
||||
- `wasi`: remove wasm build tag
|
||||
* **boards**
|
||||
- `feather-rp2040`: add support for this board
|
||||
- `feather-nrf52840-sense`: add board definition for this board
|
||||
- `pca10059`: support flashing from Windows
|
||||
- `nano-rp2040`: add this board
|
||||
- `nano-33-ble`: add support for this board
|
||||
- `pico`: add the Raspberry Pi Pico board with the new RP2040 chip
|
||||
- `qtpy`: add pin for neopixels
|
||||
- all: add definition for ws2812 for supported boards
|
||||
|
||||
|
||||
0.18.0
|
||||
---
|
||||
|
||||
* **command line**
|
||||
- drop support for Go 1.11 and 1.12
|
||||
- throw an error when no target is specified on Windows
|
||||
- improve error messages in `getDefaultPort()`, support for multiple ports
|
||||
- remove `-cflags` and `-ldflags` flags
|
||||
- implement `-ldflags="-X ..."`
|
||||
- add `-print-allocs` flag that lets you print all heap allocations
|
||||
- openocd commands in tinygo command line
|
||||
- add `-llvm-features` parameter
|
||||
- match `go test` output
|
||||
- discover USB ports only, this will ignore f.ex. bluetooth
|
||||
- use physicmal path instead of cached GOROOT in function getGoroot
|
||||
- add goroot for snap installs
|
||||
* **compiler**
|
||||
- `builder`: add support for `-opt=0`
|
||||
- `builder`, `compiler`: compile and cache packages in parallel
|
||||
- `builder`: run interp per package
|
||||
- `builder`: cache C and assembly file outputs
|
||||
- `builder`: add support for `-x` flag to print commands
|
||||
- `builder`: add optsize attribute while building the package
|
||||
- `builder`: run function passes per package
|
||||
- `builder`: hard code Clang compiler
|
||||
- `compiler`: do not use `llvm.GlobalContext()`
|
||||
- `compiler`: remove SimpleDCE pass
|
||||
- `compiler`: do not emit nil checks for `*ssa.Alloc` instructions
|
||||
- `compiler`: merge `runtime.typecodeID` and runtime.typeInInterface
|
||||
- `compiler`: do not check for impossible type asserts
|
||||
- `compiler`: fix use of global context: `llvm.Int32Type()`
|
||||
- `compiler`: add interface IR test
|
||||
- `compiler`: fix lack of method name in interface matching
|
||||
- `compiler`: fix "fragment covers entire variable" bug
|
||||
- `compiler`: optimize string literals and globals
|
||||
- `compiler`: decouple func lowering from interface type codes
|
||||
- `compiler`: add function attributes to some runtime calls
|
||||
- `compiler`: improve position information in error messages
|
||||
- `cgo`: add support for CFLAGS in .c files
|
||||
- `interp`: support GEP on fixed (MMIO) addresses
|
||||
- `interp`: handle `(reflect.Type).Elem()`
|
||||
- `interp`: add support for runtime.interfaceMethod
|
||||
- `interp`: make toLLVMValue return an error instead of panicking
|
||||
- `interp`: add support for switch statement
|
||||
- `interp`: fix phi instruction
|
||||
- `interp`: remove map support
|
||||
- `interp`: support extractvalue/insertvalue with multiple operands
|
||||
- `transform`: optimize string comparisons against ""
|
||||
- `transform`: optimize `reflect.Type` `Implements()` method
|
||||
- `transform`: fix bug in interface lowering when signatures are renamed
|
||||
- `transform`: don't rely on struct name of `runtime.typecodeID`
|
||||
- `transform`: use IPSCCP pass instead of the constant propagation pass
|
||||
- `transform`: fix func lowering assertion failure
|
||||
- `transform`: do not lower zero-sized alloc to alloca
|
||||
- `transform`: split interface and reflect lowering
|
||||
* **standard library**
|
||||
- `runtime`: add dummy debug package
|
||||
- `machine`: fix data shift/mask in newUSBSetup
|
||||
- `machine`: make `machine.I2C0` and similar objects pointers
|
||||
- `machine`: unify usbcdc code
|
||||
- `machine`: refactor PWM support
|
||||
- `machine`: avoid heap allocations in USB code
|
||||
- `reflect`: let `reflect.Type` be of interface type
|
||||
- `reflect`: implement a number of stub functions
|
||||
- `reflect`: check for access in the `Interface` method call
|
||||
- `reflect`: fix `AssignableTo` and `Implements` methods
|
||||
- `reflect`: implement `Value.CanAddr`
|
||||
- `reflect`: implement `Sizeof` and `Alignof` for func values
|
||||
- `reflect`: implement `New` function
|
||||
- `runtime`: implement command line arguments in hosted environments
|
||||
- `runtime`: implement environment variables for Linux
|
||||
- `runtime`: improve timers on nrf, and samd chips
|
||||
* **targets**
|
||||
- all: use -Qunused-arguments only for assembly files
|
||||
- `atmega1280`: add PWM support
|
||||
- `attiny`: remove dummy UART
|
||||
- `atsamd21`: improve SPI
|
||||
- `atsamd51`: fix PWM support in atsamd51p20
|
||||
- `atsamd5x`: improve SPI
|
||||
- `atsamd51`, `atsame5x`: unify samd51 and same5x
|
||||
- `atsamd51`, `atsamd21`: fix `ADC.Get()` value at 8bit and 10bit
|
||||
- `atsame5x`: add support for CAN
|
||||
- `avr`: remove I2C stubs from attiny support
|
||||
- `cortexm`: check for `arm-none-eabi-gdb` and `gdb-multiarch` commands
|
||||
- `cortexm`: add `__isr_vector` symbol
|
||||
- `cortexm`: disable FPU on Cortex-M4
|
||||
- `cortexm`: clean up Cortex-M target files
|
||||
- `fe310`: fix SPI read
|
||||
- `gameboy-advance`: Fix RGBA color interpretation
|
||||
- `nrf52833`: add PWM support
|
||||
- `stm32l0`: use unified UART logic
|
||||
- `stm32`: move f103 (bluepill) to common i2c code
|
||||
- `stm32`: separate altfunc selection for UART Tx/Rx
|
||||
- `stm32`: i2c implementation for F7, L5 and L4 MCUs
|
||||
- `stm32`: make SPI CLK fast to fix data issue
|
||||
- `stm32`: support SPI on L4 series
|
||||
- `unix`: avoid possible heap allocation with `-opt=0`
|
||||
- `unix`: use conservative GC by default
|
||||
- `unix`: use the tasks scheduler instead of coroutines
|
||||
- `wasi`: upgrade WASI version to wasi_snapshot_preview1
|
||||
- `wasi`: darwin: support basic file io based on libc
|
||||
- `wasm`: only export explicitly exported functions
|
||||
- `wasm`: use WASI ABI for exit function
|
||||
- `wasm`: scan globals conservatively
|
||||
* **boards**
|
||||
- `arduino-mega1280`: add support for the Arduino Mega 1280
|
||||
- `arduino-nano-new`: Add Arduino Nano w/ New Bootloader target
|
||||
- `atsame54-xpro`: add initial support this board
|
||||
- `feather-m4-can`: add initial support for this board
|
||||
- `grandcentral-m4`: add board support for Adafruit Grand Central M4 (SAMD51)
|
||||
- `lgt92`: update to new UART structure
|
||||
- `microbit`: remove LED constant
|
||||
- `microbit-v2`: add support for S113 SoftDevice
|
||||
- `nucleol432`: add support for this board
|
||||
- `nucleo-l031k6`: add this board
|
||||
- `pca10059`: initial support for this board
|
||||
- `qtpy`: fix msd-volume-name
|
||||
- `qtpy`: fix i2c setting
|
||||
- `teensy40`: move txBuffer allocation to UART declaration
|
||||
- `teensy40`: add UART0 as alias for UART1
|
||||
|
||||
|
||||
0.17.0
|
||||
|
||||
---
|
||||
|
||||
+1
-52
@@ -1,52 +1 @@
|
||||
# How to contribute
|
||||
|
||||
Thank you for your interest in improving TinyGo.
|
||||
|
||||
We would like your help to make this project better, so we appreciate any contributions. See if one of the following descriptions matches your situation:
|
||||
|
||||
### New to TinyGo
|
||||
|
||||
We'd love to get your feedback on getting started with TinyGo. Run into any difficulty, confusion, or anything else? You are not alone. We want to know about your experience, so we can help the next people. Please open a Github issue with your questions, or you can also get in touch directly with us on our Slack channel at [https://gophers.slack.com/messages/CDJD3SUP6](https://gophers.slack.com/messages/CDJD3SUP6).
|
||||
|
||||
### Something in TinyGo is not working as you expect
|
||||
|
||||
Please open a Github issue with your problem, and we will be happy to assist.
|
||||
|
||||
### Something in Go that you want/need does not appear to be in TinyGo
|
||||
|
||||
We probably have not implemented it yet. Please take a look at our [Roadmap](https://github.com/tinygo-org/tinygo/wiki/Roadmap). Your pull request adding the functionality to TinyGo would be greatly appreciated.
|
||||
|
||||
Please open a Github issue. We want to help, and also make sure that there is no duplications of efforts. Sometimes what you need is already being worked on by someone else.
|
||||
|
||||
A long tail of small (and large) language features haven't been implemented yet. In almost all cases, the compiler will show a `todo:` error from `compiler/compiler.go` when you try to use it. You can try implementing it, or open a bug report with a small code sample that fails to compile.
|
||||
|
||||
### Some specific hardware you want to use does not appear to be in TinyGo
|
||||
|
||||
As above, we probably have not implemented it yet. Your contribution adding the hardware support to TinyGo would be greatly appreciated.
|
||||
|
||||
Please start by opening a Github issue. We want to help you to help us to help you.
|
||||
|
||||
Lots of targets/boards are still unsupported. Adding an architecture often requires a few compiler changes, but if the architecture is supported you can try implementing support for a new chip or board in `src/runtime`. For details, see [this wiki entry on adding archs/chips/boards](https://github.com/tinygo-org/tinygo/wiki/Adding-a-new-board).
|
||||
|
||||
Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't have an implementation yet in the `machine` package. Adding support for new peripherals is very useful.
|
||||
|
||||
## How to use our Github repository
|
||||
|
||||
The `release` branch of this repo will always have the latest released version of TinyGo. All of the active development work for the next release will take place in the `dev` branch. TinyGo will use semantic versioning and will create a tag/release for each release.
|
||||
|
||||
Here is how to contribute back some code or documentation:
|
||||
|
||||
- Fork repo
|
||||
- Create a feature branch off of the `dev` branch
|
||||
- Make some useful change
|
||||
- Make sure the tests still pass
|
||||
- Submit a pull request against the `dev` branch.
|
||||
- Be kind
|
||||
|
||||
## How to run tests
|
||||
|
||||
To run the tests:
|
||||
|
||||
```
|
||||
make test
|
||||
```
|
||||
Please take a look at our [Contributing](https://tinygo.org/docs/guides/contributing/) page on our web site for details. Thank you.
|
||||
|
||||
+38
-67
@@ -1,79 +1,50 @@
|
||||
# TinyGo base stage installs the most recent Go 1.15.x, LLVM 11 and the TinyGo compiler itself.
|
||||
FROM golang:1.15 AS tinygo-base
|
||||
# tinygo-llvm stage obtains the llvm source for TinyGo
|
||||
FROM golang:1.17 AS tinygo-llvm
|
||||
|
||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
|
||||
RUN apt-get update && \
|
||||
apt-get install -y apt-utils make cmake clang-11 binutils-avr gcc-avr avr-libc ninja-build
|
||||
|
||||
COPY ./Makefile /tinygo/Makefile
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
make llvm-source
|
||||
|
||||
# tinygo-llvm-build stage build the custom llvm with xtensa support
|
||||
FROM tinygo-llvm AS tinygo-llvm-build
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
make llvm-build
|
||||
|
||||
# tinygo-xtensa stage installs tools needed for ESP32
|
||||
FROM tinygo-llvm-build AS tinygo-xtensa
|
||||
|
||||
ARG xtensa_version="1.22.0-80-g6c4433a-5.2.0"
|
||||
RUN cd /tmp/ && \
|
||||
wget -q https://dl.espressif.com/dl/xtensa-esp32-elf-linux64-${xtensa_version}.tar.gz && \
|
||||
tar xzf xtensa-esp32-elf-linux64-${xtensa_version}.tar.gz && \
|
||||
cp ./xtensa-esp32-elf/bin/xtensa-esp32-elf-ld /usr/local/bin/ && \
|
||||
rm -rf /tmp/xtensa*
|
||||
|
||||
# tinygo-compiler stage builds the compiler itself
|
||||
FROM tinygo-xtensa AS tinygo-compiler
|
||||
|
||||
COPY . /tinygo
|
||||
|
||||
# remove submodules directories and re-init them to fix any hard-coded paths
|
||||
# after copying the tinygo directory in the previous step.
|
||||
# update submodules
|
||||
RUN cd /tinygo/ && \
|
||||
rm -rf ./lib/* && \
|
||||
rm -rf ./lib/*/ && \
|
||||
git submodule sync && \
|
||||
git submodule update --init --recursive --force
|
||||
|
||||
COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/
|
||||
RUN cd /tinygo/ && \
|
||||
make
|
||||
|
||||
# tinygo-tools stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
||||
FROM tinygo-compiler AS tinygo-tools
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
go install /tinygo/
|
||||
|
||||
# tinygo-wasm stage installs the needed dependencies to compile TinyGo programs for WASM.
|
||||
FROM tinygo-base AS tinygo-wasm
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y make clang-11 libllvm11 lld-11 && \
|
||||
make wasi-libc
|
||||
|
||||
# tinygo-avr stage installs the needed dependencies to compile TinyGo programs for AVR microcontrollers.
|
||||
FROM tinygo-base AS tinygo-avr
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils make binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device-avr && \
|
||||
apt-get autoremove -y && \
|
||||
apt-get clean
|
||||
|
||||
# tinygo-arm stage installs the needed dependencies to compile TinyGo programs for ARM microcontrollers.
|
||||
FROM tinygo-base AS tinygo-arm
|
||||
|
||||
COPY --from=tinygo-base /go/bin/tinygo /go/bin/tinygo
|
||||
COPY --from=tinygo-base /tinygo/src /tinygo/src
|
||||
COPY --from=tinygo-base /tinygo/targets /tinygo/targets
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils make clang-11 && \
|
||||
make gen-device-nrf && make gen-device-stm32
|
||||
|
||||
# tinygo-all stage installs the needed dependencies to compile TinyGo programs for all platforms.
|
||||
FROM tinygo-wasm AS tinygo-all
|
||||
|
||||
COPY --from=tinygo-base /tinygo/Makefile /tinygo/
|
||||
COPY --from=tinygo-base /tinygo/tools /tinygo/tools
|
||||
COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils make clang-11 binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device
|
||||
make wasi-libc binaryen && \
|
||||
make gen-device -j4 && \
|
||||
cp build/* $GOPATH/bin/
|
||||
|
||||
CMD ["tinygo"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Copyright (c) 2018-2021 TinyGo Authors. All rights reserved.
|
||||
Copyright (c) 2018-2022 The TinyGo Authors. All rights reserved.
|
||||
|
||||
TinyGo includes portions of the Go standard library.
|
||||
Copyright (c) 2009-2021 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2009-2022 The Go Authors. All rights reserved.
|
||||
|
||||
TinyGo includes portions of LLVM, which is under the Apache License v2.0 with
|
||||
LLVM Exceptions. See https://llvm.org/LICENSE.txt for license information.
|
||||
|
||||
@@ -9,20 +9,34 @@ CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
|
||||
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
|
||||
|
||||
# Try to autodetect LLVM build tools.
|
||||
detect = $(shell command -v $(1) 2> /dev/null && echo $(1))
|
||||
CLANG ?= $(word 1,$(abspath $(call detect,llvm-build/bin/clang))$(call detect,clang-11)$(call detect,clang-10)$(call detect,clang))
|
||||
LLVM_AR ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-ar))$(call detect,llvm-ar-11)$(call detect,llvm-ar-10)$(call detect,llvm-ar))
|
||||
LLVM_NM ?= $(word 1,$(abspath $(call detect,llvm-build/bin/llvm-nm))$(call detect,llvm-nm-11)$(call detect,llvm-nm-10)$(call detect,llvm-nm))
|
||||
# Versions are listed here in descending priority order.
|
||||
LLVM_VERSIONS = 13 12 11
|
||||
errifempty = $(if $(1),$(1),$(error $(2)))
|
||||
detect = $(shell which $(call errifempty,$(firstword $(foreach p,$(2),$(shell command -v $(p) 2> /dev/null && echo $(p)))),failed to locate $(1) at any of: $(2)))
|
||||
toolSearchPathsVersion = $(1)-$(2)
|
||||
ifeq ($(shell uname -s),Darwin)
|
||||
# Also explicitly search Brew's copy, which is not in PATH by default.
|
||||
BREW_PREFIX := $(shell brew --prefix)
|
||||
toolSearchPathsVersion += $(BREW_PREFIX)/opt/llvm@$(2)/bin/$(1)-$(2) $(BREW_PREFIX)/opt/llvm@$(2)/bin/$(1)
|
||||
endif
|
||||
# First search for a custom built copy, then move on to explicitly version-tagged binaries, then just see if the tool is in path with its normal name.
|
||||
findLLVMTool = $(call detect,$(1),$(abspath llvm-build/bin/$(1)) $(foreach ver,$(LLVM_VERSIONS),$(call toolSearchPathsVersion,$(1),$(ver))) $(1))
|
||||
CLANG ?= $(call findLLVMTool,clang)
|
||||
LLVM_AR ?= $(call findLLVMTool,llvm-ar)
|
||||
LLVM_NM ?= $(call findLLVMTool,llvm-nm)
|
||||
|
||||
# Go binary and GOROOT to select
|
||||
GO ?= go
|
||||
export GOROOT = $(shell $(GO) env GOROOT)
|
||||
|
||||
# Flags to pass to go test.
|
||||
GOTESTFLAGS ?= -v
|
||||
|
||||
# md5sum binary
|
||||
MD5SUM = md5sum
|
||||
|
||||
# tinygo binary for tests
|
||||
TINYGO ?= $(word 1,$(call detect,tinygo)$(call detect,build/tinygo))
|
||||
TINYGO ?= $(call detect,tinygo,tinygo $(CURDIR)/build/tinygo)
|
||||
|
||||
# Use CCACHE for LLVM if possible
|
||||
ifneq (, $(shell command -v ccache 2> /dev/null))
|
||||
@@ -36,9 +50,9 @@ else
|
||||
LLVM_OPTION += '-DLLVM_ENABLE_ASSERTIONS=OFF'
|
||||
endif
|
||||
|
||||
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr
|
||||
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr gen-device-rp
|
||||
|
||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf debuginfopdb executionengine frontendopenmp instrumentation interpreter ipo irreader libdriver linker lto mc mcjit objcarcopts option profiledata scalaropts support target windowsmanifest
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXE = .exe
|
||||
@@ -54,24 +68,24 @@ ifeq ($(OS),Windows_NT)
|
||||
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
|
||||
CGO_LDFLAGS_EXTRA += -lversion
|
||||
|
||||
LIBCLANG_NAME = libclang
|
||||
USE_SYSTEM_BINARYEN ?= 1
|
||||
|
||||
else ifeq ($(shell uname -s),Darwin)
|
||||
MD5SUM = md5
|
||||
LIBCLANG_NAME = clang
|
||||
|
||||
USE_SYSTEM_BINARYEN ?= 1
|
||||
|
||||
else ifeq ($(shell uname -s),FreeBSD)
|
||||
MD5SUM = md5
|
||||
LIBCLANG_NAME = clang
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
else
|
||||
LIBCLANG_NAME = clang
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
endif
|
||||
|
||||
# Libraries that should be linked in for the statically linked Clang.
|
||||
CLANG_LIB_NAMES = clangAnalysis clangARCMigrate clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangStaticAnalyzerCheckers clangStaticAnalyzerCore clangStaticAnalyzerFrontend clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
|
||||
CLANG_LIB_NAMES = clangAnalysis clangAST clangASTMatchers clangBasic clangCodeGen clangCrossTU clangDriver clangDynamicASTMatchers clangEdit clangFormat clangFrontend clangFrontendTool clangHandleCXX clangHandleLLVM clangIndex clangLex clangParse clangRewrite clangRewriteFrontend clangSema clangSerialization clangTooling clangToolingASTDiff clangToolingCore clangToolingInclusions
|
||||
CLANG_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
|
||||
|
||||
# Libraries that should be linked in for the statically linked LLD.
|
||||
@@ -81,33 +95,38 @@ LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP)
|
||||
# Other libraries that are needed to link TinyGo.
|
||||
EXTRA_LIB_NAMES = LLVMInterpreter
|
||||
|
||||
# All libraries to be built and linked with the tinygo binary (lib/lib*.a).
|
||||
LIB_NAMES = clang $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)
|
||||
|
||||
# These build targets appear to be the only ones necessary to build all TinyGo
|
||||
# dependencies. Only building a subset significantly speeds up rebuilding LLVM.
|
||||
# The Makefile rules convert a name like lldELF to lib/liblldELF.a to match the
|
||||
# library path (for ninja).
|
||||
# This list also includes a few tools that are necessary as part of the full
|
||||
# TinyGo build.
|
||||
NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm $(addprefix lib/lib,$(addsuffix .a,$(LIBCLANG_NAME) $(CLANG_LIB_NAMES) $(LLD_LIB_NAMES) $(EXTRA_LIB_NAMES)))
|
||||
NINJA_BUILD_TARGETS = clang llvm-config llvm-ar llvm-nm $(addprefix lib/lib,$(addsuffix .a,$(LIB_NAMES)))
|
||||
|
||||
# For static linking.
|
||||
ifneq ("$(wildcard $(LLVM_BUILDDIR)/bin/llvm-config*)","")
|
||||
CGO_CPPFLAGS+=$(shell $(LLVM_BUILDDIR)/bin/llvm-config --cppflags) -I$(abspath $(LLVM_BUILDDIR))/tools/clang/include -I$(abspath $(CLANG_SRC))/include -I$(abspath $(LLD_SRC))/include
|
||||
CGO_CXXFLAGS=-std=c++14
|
||||
CGO_LDFLAGS+=$(abspath $(LLVM_BUILDDIR))/lib/lib$(LIBCLANG_NAME).a -L$(abspath $(LLVM_BUILDDIR)/lib) $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
|
||||
CGO_LDFLAGS+=-L$(abspath $(LLVM_BUILDDIR)/lib) -lclang $(CLANG_LIBS) $(LLD_LIBS) $(shell $(LLVM_BUILDDIR)/bin/llvm-config --ldflags --libs --system-libs $(LLVM_COMPONENTS)) -lstdc++ $(CGO_LDFLAGS_EXTRA)
|
||||
endif
|
||||
|
||||
|
||||
clean:
|
||||
@rm -rf build
|
||||
|
||||
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/internal/reflectlite transform
|
||||
FMT_PATHS = ./*.go builder cgo compiler interp loader src/device/arm src/examples src/machine src/os src/reflect src/runtime src/sync src/syscall src/testing src/internal/reflectlite transform
|
||||
fmt:
|
||||
@gofmt -l -w $(FMT_PATHS)
|
||||
fmt-check:
|
||||
@unformatted=$$(gofmt -l $(FMT_PATHS)); [ -z "$$unformatted" ] && exit 0; echo "Unformatted:"; for fn in $$unformatted; do echo " $$fn"; done; exit 1
|
||||
|
||||
|
||||
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-stm32 gen-device-kendryte gen-device-nxp
|
||||
gen-device: gen-device-avr gen-device-esp gen-device-nrf gen-device-sam gen-device-sifive gen-device-kendryte gen-device-nxp gen-device-rp
|
||||
ifneq ($(STM32), 0)
|
||||
gen-device: gen-device-stm32
|
||||
endif
|
||||
|
||||
gen-device-avr:
|
||||
@if [ ! -e lib/avr/README.md ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
|
||||
@@ -121,6 +140,7 @@ build/gen-device-svd: ./tools/gen-device-svd/*.go
|
||||
|
||||
gen-device-esp: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif-Community -interrupts=software lib/cmsis-svd/data/Espressif-Community/ src/device/esp/
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Espressif -interrupts=software lib/cmsis-svd/data/Espressif/ src/device/esp/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/esp
|
||||
|
||||
gen-device-nrf: build/gen-device-svd
|
||||
@@ -147,28 +167,40 @@ gen-device-stm32: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/tinygo-org/stm32-svd lib/stm32-svd/svd src/device/stm32/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/stm32
|
||||
|
||||
gen-device-rp: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/RaspberryPi lib/cmsis-svd/data/RaspberryPi/ src/device/rp/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/rp
|
||||
|
||||
# Get LLVM sources.
|
||||
$(LLVM_PROJECTDIR)/README.md:
|
||||
git clone -b xtensa_release_11.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
|
||||
llvm-source: $(LLVM_PROJECTDIR)/README.md
|
||||
$(LLVM_PROJECTDIR)/llvm:
|
||||
git clone -b xtensa_release_13.0.0 --depth=1 https://github.com/tinygo-org/llvm-project $(LLVM_PROJECTDIR)
|
||||
llvm-source: $(LLVM_PROJECTDIR)/llvm
|
||||
|
||||
# Configure LLVM.
|
||||
TINYGO_SOURCE_DIR=$(shell pwd)
|
||||
$(LLVM_BUILDDIR)/build.ninja: llvm-source
|
||||
mkdir -p $(LLVM_BUILDDIR); cd $(LLVM_BUILDDIR); cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF $(LLVM_OPTION)
|
||||
mkdir -p $(LLVM_BUILDDIR) && cd $(LLVM_BUILDDIR) && cmake -G Ninja $(TINYGO_SOURCE_DIR)/$(LLVM_PROJECTDIR)/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR;Xtensa" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=OFF -DLLVM_ENABLE_LIBEDIT=OFF -DLLVM_ENABLE_Z3_SOLVER=OFF -DLLVM_ENABLE_OCAMLDOC=OFF -DLLVM_ENABLE_LIBXML2=OFF -DLLVM_ENABLE_PROJECTS="clang;lld" -DLLVM_TOOL_CLANG_TOOLS_EXTRA_BUILD=OFF -DCLANG_ENABLE_STATIC_ANALYZER=OFF -DCLANG_ENABLE_ARCMT=OFF $(LLVM_OPTION)
|
||||
|
||||
# Build LLVM.
|
||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||
cd $(LLVM_BUILDDIR); ninja $(NINJA_BUILD_TARGETS)
|
||||
cd $(LLVM_BUILDDIR) && ninja $(NINJA_BUILD_TARGETS)
|
||||
|
||||
ifneq ($(USE_SYSTEM_BINARYEN),1)
|
||||
# Build Binaryen
|
||||
.PHONY: binaryen
|
||||
binaryen: build/wasm-opt$(EXE)
|
||||
build/wasm-opt$(EXE):
|
||||
mkdir -p build
|
||||
cd lib/binaryen && cmake -G Ninja . -DBUILD_STATIC_LIB=ON $(BINARYEN_OPTION) && ninja bin/wasm-opt$(EXE)
|
||||
cp lib/binaryen/bin/wasm-opt$(EXE) build/wasm-opt$(EXE)
|
||||
endif
|
||||
|
||||
# Build wasi-libc sysroot
|
||||
.PHONY: wasi-libc
|
||||
wasi-libc: lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a
|
||||
lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a:
|
||||
@if [ ! -e lib/wasi-libc/Makefile ]; then echo "Submodules have not been downloaded. Please download them using:\n git submodule update --init"; exit 1; fi
|
||||
cd lib/wasi-libc && make -j4 WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
|
||||
cd lib/wasi-libc && make -j4 WASM_CFLAGS="-O2 -g -DNDEBUG" WASM_CC=$(CLANG) WASM_AR=$(LLVM_AR) WASM_NM=$(LLVM_NM)
|
||||
|
||||
|
||||
# Build the Go compiler.
|
||||
@@ -177,31 +209,106 @@ tinygo:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -buildmode exe -o build/tinygo$(EXE) -tags byollvm -ldflags="-X main.gitSha1=`git rev-parse --short HEAD`" .
|
||||
|
||||
test: wasi-libc
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=20m -buildmode exe -tags byollvm ./builder ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
|
||||
# Tests that take over a minute in wasi
|
||||
TEST_PACKAGES_SLOW = \
|
||||
compress/bzip2 \
|
||||
compress/flate \
|
||||
crypto/dsa \
|
||||
index/suffixarray \
|
||||
|
||||
TEST_PACKAGES_BASE = \
|
||||
compress/zlib \
|
||||
container/heap \
|
||||
container/list \
|
||||
container/ring \
|
||||
crypto/des \
|
||||
crypto/elliptic/internal/fiat \
|
||||
crypto/internal/subtle \
|
||||
crypto/md5 \
|
||||
crypto/rc4 \
|
||||
crypto/sha1 \
|
||||
crypto/sha256 \
|
||||
crypto/sha512 \
|
||||
debug/macho \
|
||||
encoding \
|
||||
encoding/ascii85 \
|
||||
encoding/base32 \
|
||||
encoding/csv \
|
||||
encoding/hex \
|
||||
go/scanner \
|
||||
hash \
|
||||
hash/adler32 \
|
||||
hash/crc64 \
|
||||
hash/fnv \
|
||||
html \
|
||||
internal/itoa \
|
||||
internal/profile \
|
||||
math \
|
||||
math/cmplx \
|
||||
net/http/internal/ascii \
|
||||
net/mail \
|
||||
os \
|
||||
path \
|
||||
reflect \
|
||||
sync \
|
||||
testing \
|
||||
testing/iotest \
|
||||
text/scanner \
|
||||
unicode \
|
||||
unicode/utf16 \
|
||||
unicode/utf8 \
|
||||
|
||||
# Standard library packages that pass tests natively
|
||||
TEST_PACKAGES := \
|
||||
$(TEST_PACKAGES_BASE)
|
||||
|
||||
# archive/zip requires ReadAt, which is not yet supported on windows
|
||||
ifneq ($(OS),Windows_NT)
|
||||
TEST_PACKAGES := \
|
||||
$(TEST_PACKAGES) \
|
||||
archive/zip
|
||||
endif
|
||||
|
||||
# Standard library packages that pass tests on wasi
|
||||
TEST_PACKAGES_WASI = \
|
||||
$(TEST_PACKAGES_BASE)
|
||||
|
||||
# Test known-working standard library packages.
|
||||
# TODO: do this in one command, parallelize, and only show failing tests (no
|
||||
# implied -v flag).
|
||||
# TODO: parallelize, and only show failing tests (no implied -v flag).
|
||||
.PHONY: tinygo-test
|
||||
tinygo-test:
|
||||
$(TINYGO) test container/heap
|
||||
$(TINYGO) test container/list
|
||||
$(TINYGO) test container/ring
|
||||
$(TINYGO) test crypto/des
|
||||
$(TINYGO) test encoding/ascii85
|
||||
$(TINYGO) test encoding/base32
|
||||
$(TINYGO) test encoding/hex
|
||||
$(TINYGO) test hash/adler32
|
||||
$(TINYGO) test hash/fnv
|
||||
$(TINYGO) test hash/crc64
|
||||
$(TINYGO) test math
|
||||
$(TINYGO) test math/cmplx
|
||||
$(TINYGO) test text/scanner
|
||||
$(TINYGO) test unicode/utf8
|
||||
$(TINYGO) test $(TEST_PACKAGES) $(TEST_PACKAGES_SLOW)
|
||||
tinygo-test-fast:
|
||||
$(TINYGO) test $(TEST_PACKAGES)
|
||||
tinygo-bench:
|
||||
$(TINYGO) test -bench . $(TEST_PACKAGES) $(TEST_PACKAGES_SLOW)
|
||||
tinygo-bench-fast:
|
||||
$(TINYGO) test -bench . $(TEST_PACKAGES)
|
||||
tinygo-test-wasi:
|
||||
$(TINYGO) test -target wasi $(TEST_PACKAGES_WASI) $(TEST_PACKAGES_SLOW)
|
||||
tinygo-test-wasi-fast:
|
||||
$(TINYGO) test -target wasi $(TEST_PACKAGES_WASI)
|
||||
tinygo-bench-wasi:
|
||||
$(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_WASI) $(TEST_PACKAGES_SLOW)
|
||||
tinygo-bench-wasi-fast:
|
||||
$(TINYGO) test -target wasi -bench . $(TEST_PACKAGES_WASI)
|
||||
|
||||
# Test external packages in a large corpus.
|
||||
test-corpus:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml
|
||||
test-corpus-fast:
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus -short . -corpus=testdata/corpus.yaml
|
||||
test-corpus-wasi: wasi-libc
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test $(GOTESTFLAGS) -timeout=1h -buildmode exe -tags byollvm -run TestCorpus . -corpus=testdata/corpus.yaml -target=wasi
|
||||
|
||||
|
||||
.PHONY: smoketest
|
||||
smoketest:
|
||||
$(TINYGO) version
|
||||
# regression test for #2563
|
||||
cd tests/os/smoke && $(TINYGO) test -c -target=pybadge && rm smoke.test
|
||||
# test all examples (except pwm)
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -221,9 +328,9 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/memstats
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -234,18 +341,20 @@ smoketest:
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/test
|
||||
@$(MD5SUM) test.hex
|
||||
# test simulated boards on play.tinygo.org
|
||||
$(TINYGO) build -o test.wasm -tags=arduino examples/blinky1
|
||||
ifneq ($(WASM), 0)
|
||||
$(TINYGO) build -size short -o test.wasm -tags=arduino examples/blinky1
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=hifive1b examples/blinky1
|
||||
$(TINYGO) build -size short -o test.wasm -tags=hifive1b examples/blinky1
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=reelboard examples/blinky1
|
||||
$(TINYGO) build -size short -o test.wasm -tags=reelboard examples/blinky1
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=pca10040 examples/blinky2
|
||||
$(TINYGO) build -size short -o test.wasm -tags=pca10040 examples/blinky2
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=pca10056 examples/blinky2
|
||||
$(TINYGO) build -size short -o test.wasm -tags=pca10056 examples/blinky2
|
||||
@$(MD5SUM) test.wasm
|
||||
$(TINYGO) build -o test.wasm -tags=circuitplay_express examples/blinky1
|
||||
$(TINYGO) build -size short -o test.wasm -tags=circuitplay_express examples/blinky1
|
||||
@$(MD5SUM) test.wasm
|
||||
endif
|
||||
# test all targets/boards
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040-s132v6 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -253,12 +362,14 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit-s110v8 examples/echo
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit-v2-s113v7 examples/microbit-blink
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nrf52840-mdk examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=reelboard examples/blinky2
|
||||
@@ -267,6 +378,10 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10056 examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10059 examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m0 examples/blinky1
|
||||
@@ -275,14 +390,6 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-bluefruit examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/i2s
|
||||
@@ -291,6 +398,8 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.gba -target=gameboy-advance examples/gba-display
|
||||
@$(MD5SUM) test.gba
|
||||
$(TINYGO) build -size short -o test.hex -target=grandcentral-m4 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/blinky1
|
||||
@@ -307,12 +416,8 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=particle-xenon examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pinetime-devkit0 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=lgt92 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=x9pro examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10056-s140v7 examples/blinky1
|
||||
@@ -331,20 +436,38 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-nrf52840-sense examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-nrf52840 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=qtpy examples/blinky1
|
||||
$(TINYGO) build -size short -o test.hex -target=qtpy examples/serial
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=teensy40 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=teensy36 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=p1am-100 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=atsame54-xpro examples/can
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m4-can examples/caninterrupt
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-nano33 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-mkrwifi1010 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pico examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nano-33-ble examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nano-rp2040 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-rp2040 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
# test pwm
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -352,8 +475,40 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-m4 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pyportal examples/pwm
|
||||
ifneq ($(STM32), 0)
|
||||
$(TINYGO) build -size short -o test.hex -target=bluepill examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=feather-stm32f405 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=lgt92 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f103rb examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-f722ze examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l031k6 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l432kc examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-l552ze examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=nucleo-wl55jc examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco examples/blinky2
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f4disco-1 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=stm32f469disco examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=lorae5 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=swan examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
endif
|
||||
ifneq ($(AVR), 0)
|
||||
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -363,6 +518,10 @@ ifneq ($(AVR), 0)
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino -scheduler=tasks examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-mega1280 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino-nano examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
|
||||
@@ -375,61 +534,119 @@ ifneq ($(XTENSA), 0)
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target m5stack-core2 examples/serial
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target m5stack examples/serial
|
||||
@$(MD5SUM) test.bin
|
||||
endif
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32c3 examples/serial
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=m5stamp-c3 examples/serial
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.hex -target=hifive1b examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=hifive1-qemu examples/serial
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=maixbit examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/export
|
||||
$(TINYGO) build -o wasm.wasm -target=wasm examples/wasm/main
|
||||
ifneq ($(WASM), 0)
|
||||
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/export
|
||||
$(TINYGO) build -size short -o wasm.wasm -target=wasm examples/wasm/main
|
||||
endif
|
||||
# test various compiler flags
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -gc=none -scheduler=none examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=1 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -serial=none examples/echo
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -o test.nro -target=nintendoswitch examples/serial
|
||||
@$(MD5SUM) test.nro
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 -opt=0 ./testdata/stdlib.go
|
||||
@$(MD5SUM) test.hex
|
||||
GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo
|
||||
GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo
|
||||
ifneq ($(OS),Windows_NT)
|
||||
# TODO: this does not yet work on Windows. Somehow, unused functions are
|
||||
# not garbage collected.
|
||||
$(TINYGO) build -o test.elf -gc=leaking -scheduler=none examples/serial
|
||||
endif
|
||||
|
||||
|
||||
wasmtest:
|
||||
$(GO) test ./tests/wasm
|
||||
|
||||
build/release: tinygo gen-device wasi-libc
|
||||
build/release: tinygo gen-device wasi-libc $(if $(filter 1,$(USE_SYSTEM_BINARYEN)),,binaryen)
|
||||
@mkdir -p build/release/tinygo/bin
|
||||
@mkdir -p build/release/tinygo/lib/clang/include
|
||||
@mkdir -p build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@mkdir -p build/release/tinygo/lib/compiler-rt/lib
|
||||
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
||||
@mkdir -p build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
|
||||
@mkdir -p build/release/tinygo/lib/musl/arch
|
||||
@mkdir -p build/release/tinygo/lib/musl/crt
|
||||
@mkdir -p build/release/tinygo/lib/musl/src
|
||||
@mkdir -p build/release/tinygo/lib/nrfx
|
||||
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
|
||||
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libm
|
||||
@mkdir -p build/release/tinygo/lib/wasi-libc
|
||||
@mkdir -p build/release/tinygo/pkg/armv6m-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/armv7m-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/armv7em-none-eabi
|
||||
@mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0
|
||||
@mkdir -p build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus
|
||||
@mkdir -p build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4
|
||||
@echo copying source files
|
||||
@cp -p build/tinygo$(EXE) build/release/tinygo/bin
|
||||
ifneq ($(USE_SYSTEM_BINARYEN),1)
|
||||
@cp -p build/wasm-opt$(EXE) build/release/tinygo/bin
|
||||
endif
|
||||
@cp -p $(abspath $(CLANG_SRC))/lib/Headers/*.h build/release/tinygo/lib/clang/include
|
||||
@cp -rp lib/CMSIS/CMSIS/Include build/release/tinygo/lib/CMSIS/CMSIS
|
||||
@cp -rp lib/CMSIS/README.md build/release/tinygo/lib/CMSIS
|
||||
@cp -rp lib/compiler-rt/lib/builtins build/release/tinygo/lib/compiler-rt/lib
|
||||
@cp -rp lib/compiler-rt/LICENSE.TXT build/release/tinygo/lib/compiler-rt
|
||||
@cp -rp lib/compiler-rt/README.txt build/release/tinygo/lib/compiler-rt
|
||||
@cp -rp lib/musl/arch/aarch64 build/release/tinygo/lib/musl/arch
|
||||
@cp -rp lib/musl/arch/arm build/release/tinygo/lib/musl/arch
|
||||
@cp -rp lib/musl/arch/generic build/release/tinygo/lib/musl/arch
|
||||
@cp -rp lib/musl/arch/i386 build/release/tinygo/lib/musl/arch
|
||||
@cp -rp lib/musl/arch/x86_64 build/release/tinygo/lib/musl/arch
|
||||
@cp -rp lib/musl/crt/crt1.c build/release/tinygo/lib/musl/crt
|
||||
@cp -rp lib/musl/COPYRIGHT build/release/tinygo/lib/musl
|
||||
@cp -rp lib/musl/include build/release/tinygo/lib/musl
|
||||
@cp -rp lib/musl/src/env build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/errno build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/exit build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/include build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/internal build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/malloc build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/mman build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/signal build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/stdio build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/string build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/thread build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/time build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/musl/src/unistd build/release/tinygo/lib/musl/src
|
||||
@cp -rp lib/mingw-w64/mingw-w64-crt/def-include build/release/tinygo/lib/mingw-w64/mingw-w64-crt
|
||||
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/api-ms-win-crt-* build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
||||
@cp -rp lib/mingw-w64/mingw-w64-crt/lib-common/kernel32.def.in build/release/tinygo/lib/mingw-w64/mingw-w64-crt/lib-common
|
||||
@cp -rp lib/mingw-w64/mingw-w64-headers/crt/ build/release/tinygo/lib/mingw-w64/mingw-w64-headers
|
||||
@cp -rp lib/mingw-w64/mingw-w64-headers/defaults/include build/release/tinygo/lib/mingw-w64/mingw-w64-headers/defaults
|
||||
@cp -rp lib/nrfx/* build/release/tinygo/lib/nrfx
|
||||
@cp -rp lib/picolibc/newlib/libc/ctype build/release/tinygo/lib/picolibc/newlib/libc
|
||||
@cp -rp lib/picolibc/newlib/libc/include build/release/tinygo/lib/picolibc/newlib/libc
|
||||
@cp -rp lib/picolibc/newlib/libc/locale build/release/tinygo/lib/picolibc/newlib/libc
|
||||
@cp -rp lib/picolibc/newlib/libc/string build/release/tinygo/lib/picolibc/newlib/libc
|
||||
@cp -rp lib/picolibc/newlib/libc/tinystdio build/release/tinygo/lib/picolibc/newlib/libc
|
||||
@cp -rp lib/picolibc-include build/release/tinygo/lib
|
||||
@cp -rp lib/picolibc/newlib/libm/common build/release/tinygo/lib/picolibc/newlib/libm
|
||||
@cp -rp lib/picolibc-stdio.c build/release/tinygo/lib
|
||||
@cp -rp lib/wasi-libc/sysroot build/release/tinygo/lib/wasi-libc/sysroot
|
||||
@cp -rp src build/release/tinygo/src
|
||||
@cp -rp targets build/release/tinygo/targets
|
||||
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a compiler-rt
|
||||
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a compiler-rt
|
||||
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a compiler-rt
|
||||
./build/tinygo build-library -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/picolibc.a picolibc
|
||||
./build/tinygo build-library -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/picolibc.a picolibc
|
||||
./build/tinygo build-library -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/picolibc.a picolibc
|
||||
./build/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/compiler-rt compiler-rt
|
||||
./build/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/compiler-rt compiler-rt
|
||||
./build/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/compiler-rt compiler-rt
|
||||
./build/tinygo build-library -target=cortex-m0 -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0/picolibc picolibc
|
||||
./build/tinygo build-library -target=cortex-m0plus -o build/release/tinygo/pkg/thumbv6m-unknown-unknown-eabi-cortex-m0plus/picolibc picolibc
|
||||
./build/tinygo build-library -target=cortex-m4 -o build/release/tinygo/pkg/thumbv7em-unknown-unknown-eabi-cortex-m4/picolibc picolibc
|
||||
|
||||
release: build/release
|
||||
tar -czf build/release.tar.gz -C build/release tinygo
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# TinyGo - Go compiler for small places
|
||||
|
||||
[](https://circleci.com/gh/tinygo-org/tinygo/tree/dev) [](https://dev.azure.com/tinygo/tinygo/_build/latest?definitionId=1&branchName=dev)
|
||||
[](https://github.com/tinygo-org/tinygo/actions/workflows/linux.yml) [](https://github.com/tinygo-org/tinygo/actions/workflows/build-macos.yml) [](https://github.com/tinygo-org/tinygo/actions/workflows/windows.yml) [](https://github.com/tinygo-org/tinygo/actions/workflows/docker.yml) [](https://circleci.com/gh/tinygo-org/tinygo/tree/dev)
|
||||
|
||||
TinyGo is a Go compiler intended for use in small places such as microcontrollers, WebAssembly (Wasm), and command-line tools.
|
||||
|
||||
@@ -43,15 +43,19 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
|
||||
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
|
||||
The following 55 microcontroller boards are currently supported:
|
||||
The following 80 microcontroller boards are currently supported:
|
||||
|
||||
* [Adafruit Circuit Playground Bluefruit](https://www.adafruit.com/product/4333)
|
||||
* [Adafruit Circuit Playground Express](https://www.adafruit.com/product/3333)
|
||||
* [Adafruit CLUE](https://www.adafruit.com/product/4500)
|
||||
* [Adafruit Feather M0](https://www.adafruit.com/product/2772)
|
||||
* [Adafruit Feather M4](https://www.adafruit.com/product/3857)
|
||||
* [Adafruit Feather M4 CAN](https://www.adafruit.com/product/4759)
|
||||
* [Adafruit Feather nRF52840 Express](https://www.adafruit.com/product/4062)
|
||||
* [Adafruit Feather nRF52840 Sense](https://www.adafruit.com/product/4516)
|
||||
* [Adafruit Feather RP2040](https://www.adafruit.com/product/4884)
|
||||
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
|
||||
* [Adafruit Grand Central M4](https://www.adafruit.com/product/4064)
|
||||
* [Adafruit ItsyBitsy M0](https://www.adafruit.com/product/3727)
|
||||
* [Adafruit ItsyBitsy M4](https://www.adafruit.com/product/3800)
|
||||
* [Adafruit ItsyBitsy nRF52840](https://www.adafruit.com/product/4481)
|
||||
@@ -62,26 +66,39 @@ The following 55 microcontroller boards are currently supported:
|
||||
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
|
||||
* [Adafruit QT Py](https://www.adafruit.com/product/4600)
|
||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||
* [Arduino Mega 1280](https://www.arduino.cc/en/Main/arduinoBoardMega/)
|
||||
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
|
||||
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
|
||||
* [Arduino MKR WiFi 1010](https://store.arduino.cc/usa/mkr-wifi-1010)
|
||||
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
|
||||
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||
* [Arduino Nano 33 BLE](https://store.arduino.cc/nano-33-ble)
|
||||
* [Arduino Nano 33 BLE Sense](https://store.arduino.cc/nano-33-ble-sense)
|
||||
* [Arduino Nano 33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||
* [Arduino Nano RP2040 Connect](https://store.arduino.cc/nano-rp2040-connect)
|
||||
* [Arduino Uno](https://store.arduino.cc/arduino-uno-rev3)
|
||||
* [Arduino Zero](https://store.arduino.cc/usa/arduino-zero)
|
||||
* [BBC micro:bit](https://microbit.org/)
|
||||
* [BBC micro:bit v2](https://microbit.org/new-microbit/)
|
||||
* [blues wireless Swan](https://blues.io/products/swan/)
|
||||
* [Digispark](http://digistump.com/products/1)
|
||||
* [Dragino LoRaWAN GPS Tracker LGT-92](http://www.dragino.com/products/lora-lorawan-end-node/item/142-lgt-92.html)
|
||||
* [ESP32](https://www.espressif.com/en/products/socs/esp32)
|
||||
* [ESP8266](https://www.espressif.com/en/products/socs/esp8266)
|
||||
* [ESP32 - Core board](https://www.espressif.com/en/products/socs/esp32)
|
||||
* [ESP32 - mini32](https://www.espressif.com/en/products/socs/esp32)
|
||||
* [ESP8266 - d1mini](https://www.espressif.com/en/products/socs/esp8266)
|
||||
* [ESP8266 - NodeMCU](https://www.espressif.com/en/products/socs/esp8266)
|
||||
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
|
||||
* [M5Stack](https://docs.m5stack.com/en/core/basic)
|
||||
* [M5Stack Core2](https://shop.m5stack.com/products/m5stack-core2-esp32-iot-development-kit)
|
||||
* [M5Stamp C3](https://docs.m5stack.com/en/core/stamp_c3)
|
||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
|
||||
* [Microchip SAM E54 Xplained Pro](https://www.microchip.com/developmenttools/productdetails/atsame54-xpro)
|
||||
* [nice!nano](https://docs.nicekeyboards.com/#/nice!nano/)
|
||||
* [Nintendo Switch](https://www.nintendo.com/switch/)
|
||||
* [Nordic Semiconductor PCA10031](https://www.nordicsemi.com/eng/Products/nRF51-Dongle)
|
||||
* [Nordic Semiconductor PCA10040](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF52-DK)
|
||||
* [Nordic Semiconductor PCA10056](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK)
|
||||
* [Nordic Semiconductor pca10059](https://www.nordicsemi.com/Software-and-tools/Development-Kits/nRF52840-Dongle)
|
||||
* [Particle Argon](https://docs.particle.io/datasheets/wi-fi/argon-datasheet/)
|
||||
* [Particle Boron](https://docs.particle.io/datasheets/cellular/boron-datasheet/)
|
||||
* [Particle Xenon](https://docs.particle.io/datasheets/discontinued/xenon-datasheet/)
|
||||
@@ -90,16 +107,24 @@ The following 55 microcontroller boards are currently supported:
|
||||
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.html)
|
||||
* [PJRC Teensy 4.0](https://www.pjrc.com/store/teensy40.html)
|
||||
* [ProductivityOpen P1AM-100](https://facts-engineering.github.io/modules/P1AM-100/P1AM-100.html)
|
||||
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
|
||||
* [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/)
|
||||
* [Raytac MDBT50Q-RX Dongle (with TinyUF2 bootloader)](https://www.adafruit.com/product/5199)
|
||||
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.html)
|
||||
* [Seeed LoRa-E5 Development Kit](https://www.seeedstudio.com/LoRa-E5-Dev-Kit-p-4868.html)
|
||||
* [Seeed Sipeed MAix BiT](https://www.seeedstudio.com/Sipeed-MAix-BiT-for-RISC-V-AI-IoT-p-2872.html)
|
||||
* [SiFIve HiFive1](https://www.sifive.com/boards/hifive1)
|
||||
* [Seeed Wio Terminal](https://www.seeedstudio.com/Wio-Terminal-p-4509.html)
|
||||
* [SiFIve HiFive1 Rev B](https://www.sifive.com/boards/hifive1)
|
||||
* [ST Micro "Nucleo" F103RB](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||
* [ST Micro "Nucleo" F722ZE](https://www.st.com/en/evaluation-tools/nucleo-f722ze.html)
|
||||
* [ST Micro "Nucleo" L031K6](https://www.st.com/ja/evaluation-tools/nucleo-l031k6.html)
|
||||
* [ST Micro "Nucleo" L432KC](https://www.st.com/ja/evaluation-tools/nucleo-l432kc.html)
|
||||
* [ST Micro "Nucleo" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.html)
|
||||
* [ST Micro "Nucleo" WL55JC](https://www.st.com/en/evaluation-tools/nucleo-wl55jc.html)
|
||||
* [ST Micro STM32F103XX "Bluepill"](https://stm32-base.org/boards/STM32F103C8T6-Blue-Pill)
|
||||
* [ST Micro STM32F407 "Discovery"](https://www.st.com/en/evaluation-tools/stm32f4discovery.html)
|
||||
* [ST Micro STM32F469 "Discovery"](https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-discovery-kits/32f469idiscovery.html)
|
||||
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
|
||||
* [The Things Industries Generic Node Sensor Edition](https://www.genericnode.com/docs/sensor-edition/)
|
||||
|
||||
For more information, see [this list of boards](https://tinygo.org/microcontrollers/). Pull requests for additional support are welcome!
|
||||
|
||||
@@ -126,7 +151,7 @@ should arrive fairly quickly (under 1 min): https://invite.slack.golangbridge.or
|
||||
|
||||
Your contributions are welcome!
|
||||
|
||||
Please take a look at our [CONTRIBUTING.md](./CONTRIBUTING.md) document for details.
|
||||
Please take a look at our [Contributing](https://tinygo.org/docs/guides/contributing/) page on our web site for details.
|
||||
|
||||
## Project Scope
|
||||
|
||||
@@ -140,7 +165,6 @@ Goals:
|
||||
|
||||
Non-goals:
|
||||
|
||||
* Using more than one core.
|
||||
* Be efficient while using zillions of goroutines. However, good goroutine support is certainly a goal.
|
||||
* Be as fast as `gc`. However, LLVM will probably be better at optimizing certain things so TinyGo might actually turn out to be faster for number crunching.
|
||||
* Be able to compile every Go program out there.
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
|
||||
# be built anyway.
|
||||
trigger:
|
||||
- release
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
- job: Build
|
||||
timeoutInMinutes: 240 # 4h
|
||||
pool:
|
||||
vmImage: 'VS2017-Win2016'
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
inputs:
|
||||
version: '1.16'
|
||||
- checkout: self
|
||||
fetchDepth: 1
|
||||
- task: Cache@2
|
||||
displayName: Cache LLVM source
|
||||
inputs:
|
||||
key: llvm-source-11-windows-v1
|
||||
path: llvm-project
|
||||
- task: Bash@3
|
||||
displayName: Download LLVM source
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: make llvm-source
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache LLVM build
|
||||
inputs:
|
||||
key: llvm-build-11-windows-v4
|
||||
path: llvm-build
|
||||
- task: Bash@3
|
||||
displayName: Build LLVM
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# install dependencies
|
||||
choco install ninja
|
||||
# hack ninja to use fewer jobs
|
||||
echo -e 'C:\\ProgramData\\Chocolatey\\bin\\ninja -j4 %*' > /usr/bin/ninja.bat
|
||||
# build!
|
||||
make llvm-build
|
||||
fi
|
||||
- task: Bash@3
|
||||
displayName: Install QEMU
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: choco install qemu --version=2020.06.12
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache wasi-libc sysroot
|
||||
inputs:
|
||||
key: wasi-libc-sysroot-v4
|
||||
path: lib/wasi-libc/sysroot
|
||||
- task: Bash@3
|
||||
displayName: Build wasi-libc
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: PATH=/usr/bin:$PATH make wasi-libc
|
||||
- task: Bash@3
|
||||
displayName: Test TinyGo
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
unset GOROOT
|
||||
make test
|
||||
- task: Bash@3
|
||||
displayName: Build TinyGo release tarball
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
unset GOROOT
|
||||
make build/release -j4
|
||||
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
|
||||
displayName: Publish zip as artifact
|
||||
artifact: tinygo
|
||||
- task: Bash@3
|
||||
displayName: Smoke tests
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
unset GOROOT
|
||||
make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
|
||||
+69
-45
@@ -3,8 +3,10 @@ package builder
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -17,17 +19,12 @@ import (
|
||||
// given as a parameter. It is equivalent to the following command:
|
||||
//
|
||||
// ar -rcs <archivePath> <objs...>
|
||||
func makeArchive(archivePath string, objs []string) error {
|
||||
func makeArchive(arfile *os.File, objs []string) error {
|
||||
// Open the archive file.
|
||||
arfile, err := os.Create(archivePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer arfile.Close()
|
||||
arwriter := ar.NewWriter(arfile)
|
||||
err = arwriter.WriteGlobalHeader()
|
||||
err := arwriter.WriteGlobalHeader()
|
||||
if err != nil {
|
||||
return &os.PathError{"write ar header", archivePath, err}
|
||||
return &os.PathError{Op: "write ar header", Path: arfile.Name(), Err: err}
|
||||
}
|
||||
|
||||
// Open all object files and read the symbols for the symbol table.
|
||||
@@ -35,43 +32,55 @@ func makeArchive(archivePath string, objs []string) error {
|
||||
name string // symbol name
|
||||
fileIndex int // index into objfiles
|
||||
}{}
|
||||
objfiles := make([]struct {
|
||||
file *os.File
|
||||
archiveOffset int32
|
||||
}, len(objs))
|
||||
archiveOffsets := make([]int32, len(objs))
|
||||
for i, objpath := range objs {
|
||||
objfile, err := os.Open(objpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
objfiles[i].file = objfile
|
||||
|
||||
// Read the symbols and add them to the symbol table.
|
||||
dbg, err := elf.NewFile(objfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
symbols, err := dbg.Symbols()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, symbol := range symbols {
|
||||
bind := elf.ST_BIND(symbol.Info)
|
||||
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
|
||||
// Don't include local symbols (STB_LOCAL).
|
||||
continue
|
||||
if dbg, err := elf.NewFile(objfile); err == nil {
|
||||
symbols, err := dbg.Symbols()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC {
|
||||
// Not a function.
|
||||
// TODO: perhaps globals variables should also be included?
|
||||
continue
|
||||
for _, symbol := range symbols {
|
||||
bind := elf.ST_BIND(symbol.Info)
|
||||
if bind != elf.STB_GLOBAL && bind != elf.STB_WEAK {
|
||||
// Don't include local symbols (STB_LOCAL).
|
||||
continue
|
||||
}
|
||||
if elf.ST_TYPE(symbol.Info) != elf.STT_FUNC && elf.ST_TYPE(symbol.Info) != elf.STT_OBJECT {
|
||||
// Not a function.
|
||||
continue
|
||||
}
|
||||
// Include in archive.
|
||||
symbolTable = append(symbolTable, struct {
|
||||
name string
|
||||
fileIndex int
|
||||
}{symbol.Name, i})
|
||||
}
|
||||
// Include in archive.
|
||||
symbolTable = append(symbolTable, struct {
|
||||
name string
|
||||
fileIndex int
|
||||
}{symbol.Name, i})
|
||||
} else if dbg, err := pe.NewFile(objfile); err == nil {
|
||||
for _, symbol := range dbg.Symbols {
|
||||
if symbol.StorageClass != 2 {
|
||||
continue
|
||||
}
|
||||
if symbol.SectionNumber == 0 {
|
||||
continue
|
||||
}
|
||||
symbolTable = append(symbolTable, struct {
|
||||
name string
|
||||
fileIndex int
|
||||
}{symbol.Name, i})
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("failed to open file %s as ELF or PE/COFF: %w", objpath, err)
|
||||
}
|
||||
|
||||
// Close file, to avoid issues with too many open files (especially on
|
||||
// MacOS X).
|
||||
objfile.Close()
|
||||
}
|
||||
|
||||
// Create the symbol table buffer.
|
||||
@@ -125,7 +134,14 @@ func makeArchive(archivePath string, objs []string) error {
|
||||
}
|
||||
|
||||
// Add all object files to the archive.
|
||||
for i, objfile := range objfiles {
|
||||
var copyBuf bytes.Buffer
|
||||
for i, objpath := range objs {
|
||||
objfile, err := os.Open(objpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer objfile.Close()
|
||||
|
||||
// Store the start index, for when we'll update the symbol table with
|
||||
// the correct file start indices.
|
||||
offset, err := arfile.Seek(0, os.SEEK_CUR)
|
||||
@@ -133,17 +149,17 @@ func makeArchive(archivePath string, objs []string) error {
|
||||
return err
|
||||
}
|
||||
if int64(int32(offset)) != offset {
|
||||
return errors.New("large archives (4GB+) not supported: " + archivePath)
|
||||
return errors.New("large archives (4GB+) not supported: " + arfile.Name())
|
||||
}
|
||||
objfiles[i].archiveOffset = int32(offset)
|
||||
archiveOffsets[i] = int32(offset)
|
||||
|
||||
// Write the file header.
|
||||
st, err := objfile.file.Stat()
|
||||
st, err := objfile.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = arwriter.WriteHeader(&ar.Header{
|
||||
Name: filepath.Base(objfile.file.Name()),
|
||||
Name: filepath.Base(objfile.Name()),
|
||||
ModTime: time.Unix(0, 0),
|
||||
Uid: 0,
|
||||
Gid: 0,
|
||||
@@ -155,22 +171,30 @@ func makeArchive(archivePath string, objs []string) error {
|
||||
}
|
||||
|
||||
// Copy the file contents into the archive.
|
||||
n, err := io.Copy(arwriter, objfile.file)
|
||||
// First load all contents into a buffer, then write it all in one go to
|
||||
// the archive file. This is a bit complicated, but is necessary because
|
||||
// io.Copy can't deal with files that are of an odd size.
|
||||
copyBuf.Reset()
|
||||
n, err := io.Copy(©Buf, objfile)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("could not copy object file into ar file: %w", err)
|
||||
}
|
||||
if n != st.Size() {
|
||||
return errors.New("file modified during ar creation: " + archivePath)
|
||||
return errors.New("file modified during ar creation: " + arfile.Name())
|
||||
}
|
||||
_, err = arwriter.Write(copyBuf.Bytes())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not copy object file into ar file: %w", err)
|
||||
}
|
||||
|
||||
// File is not needed anymore.
|
||||
objfile.file.Close()
|
||||
objfile.Close()
|
||||
}
|
||||
|
||||
// Create symbol indices.
|
||||
indicesBuf := &bytes.Buffer{}
|
||||
for _, sym := range symbolTable {
|
||||
err = binary.Write(indicesBuf, binary.BigEndian, objfiles[sym.fileIndex].archiveOffset)
|
||||
err = binary.Write(indicesBuf, binary.BigEndian, archiveOffsets[sym.fileIndex])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+714
-181
File diff suppressed because it is too large
Load Diff
@@ -1,105 +0,0 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Return the newest timestamp of all the file paths passed in. Used to check
|
||||
// for stale caches.
|
||||
func cacheTimestamp(paths []string) (time.Time, error) {
|
||||
var timestamp time.Time
|
||||
for _, path := range paths {
|
||||
st, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
if timestamp.IsZero() {
|
||||
timestamp = st.ModTime()
|
||||
} else if timestamp.Before(st.ModTime()) {
|
||||
timestamp = st.ModTime()
|
||||
}
|
||||
}
|
||||
return timestamp, nil
|
||||
}
|
||||
|
||||
// Try to load a given file from the cache. Return "", nil if no cached file can
|
||||
// be found (or the file is stale), return the absolute path if there is a cache
|
||||
// and return an error on I/O errors.
|
||||
func cacheLoad(name string, sourceFiles []string) (string, error) {
|
||||
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
|
||||
cacheStat, err := os.Stat(cachepath)
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil // does not exist
|
||||
} else if err != nil {
|
||||
return "", err // cannot stat cache file
|
||||
}
|
||||
|
||||
sourceTimestamp, err := cacheTimestamp(sourceFiles)
|
||||
if err != nil {
|
||||
return "", err // cannot stat source files
|
||||
}
|
||||
|
||||
if cacheStat.ModTime().After(sourceTimestamp) {
|
||||
return cachepath, nil
|
||||
} else {
|
||||
os.Remove(cachepath)
|
||||
// stale cache
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// Store the file located at tmppath in the cache with the given name. The
|
||||
// tmppath may or may not be gone afterwards.
|
||||
func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
|
||||
// get the last modified time
|
||||
if len(sourceFiles) == 0 {
|
||||
panic("cache: no source files")
|
||||
}
|
||||
|
||||
// TODO: check the config key
|
||||
|
||||
dir := goenv.Get("GOCACHE")
|
||||
err := os.MkdirAll(dir, 0777)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cachepath := filepath.Join(dir, name)
|
||||
err = copyFile(tmppath, cachepath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cachepath, nil
|
||||
}
|
||||
|
||||
// copyFile copies the given file from src to dst. It can copy over
|
||||
// a possibly already existing file at the destination.
|
||||
func copyFile(src, dst string) error {
|
||||
inf, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer inf.Close()
|
||||
outpath := dst + ".tmp"
|
||||
outf, err := os.Create(outpath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(outf, inf)
|
||||
if err != nil {
|
||||
os.Remove(outpath)
|
||||
return err
|
||||
}
|
||||
|
||||
err = outf.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.Rename(dst+".tmp", dst)
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Test whether the Clang generated "target-cpu" and "target-features"
|
||||
// attributes match the CPU and Features property in TinyGo target files.
|
||||
func TestClangAttributes(t *testing.T) {
|
||||
var targetNames = []string{
|
||||
// Please keep this list sorted!
|
||||
"atmega328p",
|
||||
"atmega1280",
|
||||
"atmega1284p",
|
||||
"atmega2560",
|
||||
"attiny85",
|
||||
"cortex-m0",
|
||||
"cortex-m0plus",
|
||||
"cortex-m3",
|
||||
//"cortex-m33", // TODO: broken in LLVM 11, fixed in https://reviews.llvm.org/D90305
|
||||
"cortex-m4",
|
||||
"cortex-m7",
|
||||
"esp32c3",
|
||||
"fe310",
|
||||
"gameboy-advance",
|
||||
"k210",
|
||||
"nintendoswitch",
|
||||
"riscv-qemu",
|
||||
"wasi",
|
||||
"wasm",
|
||||
}
|
||||
if hasBuiltinTools {
|
||||
// hasBuiltinTools is set when TinyGo is statically linked with LLVM,
|
||||
// which also implies it was built with Xtensa support.
|
||||
targetNames = append(targetNames, "esp32", "esp8266")
|
||||
}
|
||||
for _, targetName := range targetNames {
|
||||
targetName := targetName
|
||||
t.Run(targetName, func(t *testing.T) {
|
||||
testClangAttributes(t, &compileopts.Options{Target: targetName})
|
||||
})
|
||||
}
|
||||
|
||||
for _, options := range []*compileopts.Options{
|
||||
{GOOS: "linux", GOARCH: "386"},
|
||||
{GOOS: "linux", GOARCH: "amd64"},
|
||||
{GOOS: "linux", GOARCH: "arm", GOARM: "5"},
|
||||
{GOOS: "linux", GOARCH: "arm", GOARM: "6"},
|
||||
{GOOS: "linux", GOARCH: "arm", GOARM: "7"},
|
||||
{GOOS: "linux", GOARCH: "arm64"},
|
||||
{GOOS: "darwin", GOARCH: "amd64"},
|
||||
{GOOS: "darwin", GOARCH: "arm64"},
|
||||
{GOOS: "windows", GOARCH: "amd64"},
|
||||
} {
|
||||
name := "GOOS=" + options.GOOS + ",GOARCH=" + options.GOARCH
|
||||
if options.GOARCH == "arm" {
|
||||
name += ",GOARM=" + options.GOARM
|
||||
}
|
||||
t.Run(name, func(t *testing.T) {
|
||||
testClangAttributes(t, options)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testClangAttributes(t *testing.T, options *compileopts.Options) {
|
||||
testDir := t.TempDir()
|
||||
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
|
||||
ctx := llvm.NewContext()
|
||||
defer ctx.Dispose()
|
||||
|
||||
target, err := compileopts.LoadTarget(options)
|
||||
if err != nil {
|
||||
t.Fatalf("could not load target: %s", err)
|
||||
}
|
||||
config := compileopts.Config{
|
||||
Options: options,
|
||||
Target: target,
|
||||
ClangHeaders: clangHeaderPath,
|
||||
}
|
||||
|
||||
// Create a very simple C input file.
|
||||
srcpath := filepath.Join(testDir, "test.c")
|
||||
err = ioutil.WriteFile(srcpath, []byte("int add(int a, int b) { return a + b; }"), 0o666)
|
||||
if err != nil {
|
||||
t.Fatalf("could not write target file %s: %s", srcpath, err)
|
||||
}
|
||||
|
||||
// Compile this file using Clang.
|
||||
outpath := filepath.Join(testDir, "test.bc")
|
||||
flags := append([]string{"-c", "-emit-llvm", "-o", outpath, srcpath}, config.CFlags()...)
|
||||
if config.GOOS() == "darwin" {
|
||||
// Silence some warnings that happen when testing GOOS=darwin on
|
||||
// something other than MacOS.
|
||||
flags = append(flags, "-Wno-missing-sysroot", "-Wno-incompatible-sysroot")
|
||||
}
|
||||
err = runCCompiler(flags...)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to compile %s: %s", srcpath, err)
|
||||
}
|
||||
|
||||
// Read the resulting LLVM bitcode.
|
||||
mod, err := ctx.ParseBitcodeFile(outpath)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse bitcode file %s: %s", outpath, err)
|
||||
}
|
||||
defer mod.Dispose()
|
||||
|
||||
// Check whether the LLVM target matches.
|
||||
if mod.Target() != config.Triple() {
|
||||
t.Errorf("target has LLVM triple %#v but Clang makes it LLVM triple %#v", config.Triple(), mod.Target())
|
||||
}
|
||||
|
||||
// Check the "target-cpu" and "target-features" string attribute of the add
|
||||
// function.
|
||||
add := mod.NamedFunction("add")
|
||||
var cpu, features string
|
||||
cpuAttr := add.GetStringAttributeAtIndex(-1, "target-cpu")
|
||||
featuresAttr := add.GetStringAttributeAtIndex(-1, "target-features")
|
||||
if !cpuAttr.IsNil() {
|
||||
cpu = cpuAttr.GetStringValue()
|
||||
}
|
||||
if !featuresAttr.IsNil() {
|
||||
features = featuresAttr.GetStringValue()
|
||||
}
|
||||
if cpu != config.CPU() {
|
||||
t.Errorf("target has CPU %#v but Clang makes it CPU %#v", config.CPU(), cpu)
|
||||
}
|
||||
if features != config.Features() {
|
||||
if hasBuiltinTools || runtime.GOOS != "linux" {
|
||||
// Skip this step when using an external Clang invocation on Linux.
|
||||
// The reason is that Debian has patched Clang in a way that
|
||||
// modifies the LLVM features string, changing lots of FPU/float
|
||||
// related flags. We want to test vanilla Clang, not Debian Clang.
|
||||
t.Errorf("target has LLVM features\n\t%#v\nbut Clang makes it\n\t%#v", config.Features(), features)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This TestMain is necessary because TinyGo may also be invoked to run certain
|
||||
// LLVM tools in a separate process. Not capturing these invocations would lead
|
||||
// to recursive tests.
|
||||
func TestMain(m *testing.M) {
|
||||
if len(os.Args) >= 2 {
|
||||
switch os.Args[1] {
|
||||
case "clang", "ld.lld", "wasm-ld":
|
||||
// Invoke a specific tool.
|
||||
err := RunTool(os.Args[1], os.Args[2:]...)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// Run normal tests.
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/macho"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// ReadBuildID reads the build ID from the currently running executable.
|
||||
func ReadBuildID() ([]byte, error) {
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.Open(executable)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux", "freebsd":
|
||||
// Read the GNU build id section. (Not sure about FreeBSD though...)
|
||||
file, err := elf.NewFile(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var gnuID, goID []byte
|
||||
for _, section := range file.Sections {
|
||||
if section.Type != elf.SHT_NOTE ||
|
||||
(section.Name != ".note.gnu.build-id" && section.Name != ".note.go.buildid") {
|
||||
continue
|
||||
}
|
||||
buf := make([]byte, section.Size)
|
||||
n, err := section.ReadAt(buf, 0)
|
||||
if uint64(n) != section.Size || err != nil {
|
||||
return nil, fmt.Errorf("could not read build id: %w", err)
|
||||
}
|
||||
if section.Name == ".note.gnu.build-id" {
|
||||
gnuID = buf
|
||||
} else {
|
||||
goID = buf
|
||||
}
|
||||
}
|
||||
if gnuID != nil {
|
||||
return gnuID, nil
|
||||
} else if goID != nil {
|
||||
return goID, nil
|
||||
}
|
||||
case "darwin":
|
||||
// Read the LC_UUID load command, which contains the equivalent of a
|
||||
// build ID.
|
||||
file, err := macho.NewFile(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, load := range file.Loads {
|
||||
// Unfortunately, the debug/macho package doesn't support the
|
||||
// LC_UUID command directly. So we have to read it from
|
||||
// macho.LoadBytes.
|
||||
load, ok := load.(macho.LoadBytes)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
raw := load.Raw()
|
||||
command := binary.LittleEndian.Uint32(raw)
|
||||
if command != 0x1b {
|
||||
// Looking for the LC_UUID load command.
|
||||
// LC_UUID is defined here as 0x1b:
|
||||
// https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html
|
||||
continue
|
||||
}
|
||||
return raw[4:], nil
|
||||
}
|
||||
default:
|
||||
// On other platforms (such as Windows) there isn't such a convenient
|
||||
// build ID. Luckily, Go does have an equivalent of the build ID, which
|
||||
// is stored as a special symbol named go.buildid. You can read it
|
||||
// using `go tool buildid`, but the code below extracts it directly
|
||||
// from the binary.
|
||||
// Unfortunately, because of stripping with the -w flag, no symbol
|
||||
// table might be available. Therefore, we have to scan the binary
|
||||
// directly. Luckily the build ID is always at the start of the file.
|
||||
// For details, see:
|
||||
// https://github.com/golang/go/blob/master/src/cmd/internal/buildid/buildid.go
|
||||
fileStart := make([]byte, 4096)
|
||||
_, err := io.ReadFull(f, fileStart)
|
||||
index := bytes.Index(fileStart, []byte("\xff Go build ID: \""))
|
||||
if index < 0 || index > len(fileStart)-103 {
|
||||
return nil, fmt.Errorf("could not find build id in %s", err)
|
||||
}
|
||||
buf := fileStart[index : index+103]
|
||||
if bytes.HasPrefix(buf, []byte("\xff Go build ID: \"")) && bytes.HasSuffix(buf, []byte("\"\n \xff")) {
|
||||
return buf[len("\xff Go build ID: \"") : len(buf)-1], nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("could not find build ID in %s", executable)
|
||||
}
|
||||
+5
-3
@@ -157,10 +157,12 @@ var aeabiBuiltins = []string{
|
||||
//
|
||||
// For more information, see: https://compiler-rt.llvm.org/
|
||||
var CompilerRT = Library{
|
||||
name: "compiler-rt",
|
||||
cflags: func() []string { return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"} },
|
||||
name: "compiler-rt",
|
||||
cflags: func(target, headerPath string) []string {
|
||||
return []string{"-Werror", "-Wall", "-std=c11", "-nostdlibinc"}
|
||||
},
|
||||
sourceDir: "lib/compiler-rt/lib/builtins",
|
||||
sources: func(target string) []string {
|
||||
librarySources: func(target string) []string {
|
||||
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
|
||||
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
||||
builtins = append(builtins, aeabiBuiltins...)
|
||||
|
||||
+317
@@ -0,0 +1,317 @@
|
||||
package builder
|
||||
|
||||
// This file implements a wrapper around the C compiler (Clang) which uses a
|
||||
// build cache.
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// compileAndCacheCFile compiles a C or assembly file using a build cache.
|
||||
// Compiling the same file again (if nothing changed, including included header
|
||||
// files) the output is loaded from the build cache instead.
|
||||
//
|
||||
// Its operation is a bit complex (more complex than Go package build caching)
|
||||
// because the list of file dependencies is only known after the file is
|
||||
// compiled. However, luckily compilers have a flag to write a list of file
|
||||
// dependencies in Makefile syntax which can be used for caching.
|
||||
//
|
||||
// Because of this complexity, every file has in fact two cached build outputs:
|
||||
// the file itself, and the list of dependencies. Its operation is as follows:
|
||||
//
|
||||
// depfile = hash(path, compiler, cflags, ...)
|
||||
// if depfile exists:
|
||||
// outfile = hash of all files and depfile name
|
||||
// if outfile exists:
|
||||
// # cache hit
|
||||
// return outfile
|
||||
// # cache miss
|
||||
// tmpfile = compile file
|
||||
// read dependencies (side effect of compile)
|
||||
// write depfile
|
||||
// outfile = hash of all files and depfile name
|
||||
// rename tmpfile to outfile
|
||||
//
|
||||
// There are a few edge cases that are not handled:
|
||||
// - If a file is added to an include path, that file may be included instead of
|
||||
// some other file. This would be fixed by also including lookup failures in the
|
||||
// dependencies file, but I'm not aware of a compiler which does that.
|
||||
// - The Makefile syntax that compilers output has issues, see readDepFile for
|
||||
// details.
|
||||
// - A header file may be changed to add/remove an include. This invalidates the
|
||||
// depfile but without invalidating its name. For this reason, the depfile is
|
||||
// written on each new compilation (even when it seems unnecessary). However, it
|
||||
// could in rare cases lead to a stale file fetched from the cache.
|
||||
func compileAndCacheCFile(abspath, tmpdir string, cflags []string, printCommands func(string, ...string)) (string, error) {
|
||||
// Hash input file.
|
||||
fileHash, err := hashFile(abspath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Acquire a lock (if supported).
|
||||
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), fileHash+".c.lock"))
|
||||
defer unlock()
|
||||
|
||||
// Create cache key for the dependencies file.
|
||||
buf, err := json.Marshal(struct {
|
||||
Path string
|
||||
Hash string
|
||||
Flags []string
|
||||
LLVMVersion string
|
||||
}{
|
||||
Path: abspath,
|
||||
Hash: fileHash,
|
||||
Flags: cflags,
|
||||
LLVMVersion: llvm.Version,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err) // shouldn't happen
|
||||
}
|
||||
depfileNameHashBuf := sha512.Sum512_224(buf)
|
||||
depfileNameHash := hex.EncodeToString(depfileNameHashBuf[:])
|
||||
|
||||
// Load dependencies file, if possible.
|
||||
depfileName := "dep-" + depfileNameHash + ".json"
|
||||
depfileCachePath := filepath.Join(goenv.Get("GOCACHE"), depfileName)
|
||||
depfileBuf, err := ioutil.ReadFile(depfileCachePath)
|
||||
var dependencies []string // sorted list of dependency paths
|
||||
if err == nil {
|
||||
// There is a dependency file, that's great!
|
||||
// Parse it first.
|
||||
err := json.Unmarshal(depfileBuf, &dependencies)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not parse dependencies JSON: %w", err)
|
||||
}
|
||||
|
||||
// Obtain hashes of all the files listed as a dependency.
|
||||
outpath, err := makeCFileCachePath(dependencies, depfileNameHash)
|
||||
if err == nil {
|
||||
if _, err := os.Stat(outpath); err == nil {
|
||||
return outpath, nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
} else if !os.IsNotExist(err) {
|
||||
// expected either nil or IsNotExist
|
||||
return "", err
|
||||
}
|
||||
|
||||
objTmpFile, err := ioutil.TempFile(goenv.Get("GOCACHE"), "tmp-*.o")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
objTmpFile.Close()
|
||||
depTmpFile, err := ioutil.TempFile(tmpdir, "dep-*.d")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
depTmpFile.Close()
|
||||
flags := append([]string{}, cflags...) // copy cflags
|
||||
flags = append(flags, "-MD", "-MV", "-MTdeps", "-MF", depTmpFile.Name()) // autogenerate dependencies
|
||||
flags = append(flags, "-c", "-o", objTmpFile.Name(), abspath)
|
||||
if strings.ToLower(filepath.Ext(abspath)) == ".s" {
|
||||
// If this is an assembly file (.s or .S, lowercase or uppercase), then
|
||||
// we'll need to add -Qunused-arguments because many parameters are
|
||||
// relevant to C, not assembly. And with -Werror, having meaningless
|
||||
// flags (for the assembler) is a compiler error.
|
||||
flags = append(flags, "-Qunused-arguments")
|
||||
}
|
||||
if printCommands != nil {
|
||||
printCommands("clang", flags...)
|
||||
}
|
||||
err = runCCompiler(flags...)
|
||||
if err != nil {
|
||||
return "", &commandError{"failed to build", abspath, err}
|
||||
}
|
||||
|
||||
// Create sorted and uniqued slice of dependencies.
|
||||
dependencyPaths, err := readDepFile(depTmpFile.Name())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dependencyPaths = append(dependencyPaths, abspath) // necessary for .s files
|
||||
dependencySet := make(map[string]struct{}, len(dependencyPaths))
|
||||
var dependencySlice []string
|
||||
for _, path := range dependencyPaths {
|
||||
if _, ok := dependencySet[path]; ok {
|
||||
continue
|
||||
}
|
||||
dependencySet[path] = struct{}{}
|
||||
dependencySlice = append(dependencySlice, path)
|
||||
}
|
||||
sort.Strings(dependencySlice)
|
||||
|
||||
// Write dependencies file.
|
||||
f, err := ioutil.TempFile(filepath.Dir(depfileCachePath), depfileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
buf, err = json.MarshalIndent(dependencySlice, "", "\t")
|
||||
if err != nil {
|
||||
panic(err) // shouldn't happen
|
||||
}
|
||||
_, err = f.Write(buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.Rename(f.Name(), depfileCachePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Move temporary object file to final location.
|
||||
outpath, err := makeCFileCachePath(dependencySlice, depfileNameHash)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.Rename(objTmpFile.Name(), outpath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return outpath, nil
|
||||
}
|
||||
|
||||
// Create a cache path (a path in GOCACHE) to store the output of a compiler
|
||||
// job. This path is based on the dep file name (which is a hash of metadata
|
||||
// including compiler flags) and the hash of all input files in the paths slice.
|
||||
func makeCFileCachePath(paths []string, depfileNameHash string) (string, error) {
|
||||
// Hash all input files.
|
||||
fileHashes := make(map[string]string, len(paths))
|
||||
for _, path := range paths {
|
||||
hash, err := hashFile(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
fileHashes[path] = hash
|
||||
}
|
||||
|
||||
// Calculate a cache key based on the above hashes.
|
||||
buf, err := json.Marshal(struct {
|
||||
DepfileHash string
|
||||
FileHashes map[string]string
|
||||
}{
|
||||
DepfileHash: depfileNameHash,
|
||||
FileHashes: fileHashes,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err) // shouldn't happen
|
||||
}
|
||||
outFileNameBuf := sha512.Sum512_224(buf)
|
||||
cacheKey := hex.EncodeToString(outFileNameBuf[:])
|
||||
|
||||
outpath := filepath.Join(goenv.Get("GOCACHE"), "obj-"+cacheKey+".o")
|
||||
return outpath, nil
|
||||
}
|
||||
|
||||
// hashFile hashes the given file path and returns the hash as a hex string.
|
||||
func hashFile(path string) (string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to hash file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
fileHasher := sha512.New512_224()
|
||||
_, err = io.Copy(fileHasher, f)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to hash file: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(fileHasher.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// readDepFile reads a dependency file in NMake (Visual Studio make) format. The
|
||||
// file is assumed to have a single target named deps.
|
||||
//
|
||||
// There are roughly three make syntax variants:
|
||||
// - BSD make, which doesn't support any escaping. This means that many special
|
||||
// characters are not supported in file names.
|
||||
// - GNU make, which supports escaping using a backslash but when it fails to
|
||||
// find a file it tries to fall back with the literal path name (to match BSD
|
||||
// make).
|
||||
// - NMake (Visual Studio) and Jom, which simply quote the string if there are
|
||||
// any weird characters.
|
||||
// Clang supports two variants: a format that's a compromise between BSD and GNU
|
||||
// make (and is buggy to match GCC which is equally buggy), and NMake/Jom, which
|
||||
// is at least somewhat sane. This last format isn't perfect either: it does not
|
||||
// correctly handle filenames with quote marks in them. Those are generally not
|
||||
// allowed on Windows, but of course can be used on POSIX like systems. Still,
|
||||
// it's the most sane of any of the formats so readDepFile will use that format.
|
||||
func readDepFile(filename string) ([]string, error) {
|
||||
buf, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(buf) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return parseDepFile(string(buf))
|
||||
}
|
||||
|
||||
func parseDepFile(s string) ([]string, error) {
|
||||
// This function makes no attempt at parsing anything other than Clang -MD
|
||||
// -MV output.
|
||||
|
||||
// For Windows: replace CRLF with LF to make the logic below simpler.
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n")
|
||||
|
||||
// Collapse all lines ending in a backslash. These backslashes are really
|
||||
// just a way to continue a line without making very long lines.
|
||||
s = strings.ReplaceAll(s, "\\\n", " ")
|
||||
|
||||
// Only use the first line, which is expected to begin with "deps:".
|
||||
line := strings.SplitN(s, "\n", 2)[0]
|
||||
if !strings.HasPrefix(line, "deps:") {
|
||||
return nil, errors.New("readDepFile: expected 'deps:' prefix")
|
||||
}
|
||||
line = strings.TrimSpace(line[len("deps:"):])
|
||||
|
||||
var deps []string
|
||||
for line != "" {
|
||||
if line[0] == '"' {
|
||||
// File path is quoted. Path ends with double quote.
|
||||
// This does not handle double quotes in path names, which is a
|
||||
// problem on non-Windows systems.
|
||||
line = line[1:]
|
||||
end := strings.IndexByte(line, '"')
|
||||
if end < 0 {
|
||||
return nil, errors.New("readDepFile: path is incorrectly quoted")
|
||||
}
|
||||
dep := line[:end]
|
||||
line = strings.TrimSpace(line[end+1:])
|
||||
deps = append(deps, dep)
|
||||
} else {
|
||||
// File path is not quoted. Path ends in space or EOL.
|
||||
end := strings.IndexFunc(line, unicode.IsSpace)
|
||||
if end < 0 {
|
||||
// last dependency
|
||||
deps = append(deps, line)
|
||||
break
|
||||
}
|
||||
dep := line[:end]
|
||||
line = strings.TrimSpace(line[end:])
|
||||
deps = append(deps, dep)
|
||||
}
|
||||
}
|
||||
return deps, nil
|
||||
}
|
||||
+42
-37
@@ -115,29 +115,26 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
// Any DebugInfoKind implies GenDwarfForAssembly.
|
||||
Opts.GenDwarfForAssembly = Args.hasArg(OPT_debug_info_kind_EQ);
|
||||
|
||||
if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections,
|
||||
OPT_compress_debug_sections_EQ)) {
|
||||
if (A->getOption().getID() == OPT_compress_debug_sections) {
|
||||
// TODO: be more clever about the compression type auto-detection
|
||||
Opts.CompressDebugSections = llvm::DebugCompressionType::GNU;
|
||||
} else {
|
||||
Opts.CompressDebugSections =
|
||||
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
|
||||
.Case("none", llvm::DebugCompressionType::None)
|
||||
.Case("zlib", llvm::DebugCompressionType::Z)
|
||||
.Case("zlib-gnu", llvm::DebugCompressionType::GNU)
|
||||
.Default(llvm::DebugCompressionType::None);
|
||||
}
|
||||
if (const Arg *A = Args.getLastArg(OPT_compress_debug_sections_EQ)) {
|
||||
Opts.CompressDebugSections =
|
||||
llvm::StringSwitch<llvm::DebugCompressionType>(A->getValue())
|
||||
.Case("none", llvm::DebugCompressionType::None)
|
||||
.Case("zlib", llvm::DebugCompressionType::Z)
|
||||
.Case("zlib-gnu", llvm::DebugCompressionType::GNU)
|
||||
.Default(llvm::DebugCompressionType::None);
|
||||
}
|
||||
|
||||
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
|
||||
if (auto *DwarfFormatArg = Args.getLastArg(OPT_gdwarf64, OPT_gdwarf32))
|
||||
Opts.Dwarf64 = DwarfFormatArg->getOption().matches(OPT_gdwarf64);
|
||||
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
|
||||
Opts.DwarfDebugFlags =
|
||||
std::string(Args.getLastArgValue(OPT_dwarf_debug_flags));
|
||||
Opts.DwarfDebugProducer =
|
||||
std::string(Args.getLastArgValue(OPT_dwarf_debug_producer));
|
||||
Opts.DebugCompilationDir =
|
||||
std::string(Args.getLastArgValue(OPT_fdebug_compilation_dir));
|
||||
if (const Arg *A = Args.getLastArg(options::OPT_ffile_compilation_dir_EQ,
|
||||
options::OPT_fdebug_compilation_dir_EQ))
|
||||
Opts.DebugCompilationDir = A->getValue();
|
||||
Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name));
|
||||
|
||||
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) {
|
||||
@@ -219,7 +216,7 @@ getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
|
||||
|
||||
std::error_code EC;
|
||||
auto Out = std::make_unique<raw_fd_ostream>(
|
||||
Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_Text));
|
||||
Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_TextWithCRLF));
|
||||
if (EC) {
|
||||
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
|
||||
return nullptr;
|
||||
@@ -228,7 +225,8 @@ getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
|
||||
return Out;
|
||||
}
|
||||
|
||||
bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
static bool ExecuteAssemblerImpl(AssemblerInvocation &Opts,
|
||||
DiagnosticsEngine &Diags) {
|
||||
// Get the target specific parser.
|
||||
std::string Error;
|
||||
const Target *TheTarget = TargetRegistry::lookupTarget(Opts.Triple, Error);
|
||||
@@ -236,7 +234,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
return Diags.Report(diag::err_target_unknown_triple) << Opts.Triple;
|
||||
|
||||
ErrorOr<std::unique_ptr<MemoryBuffer>> Buffer =
|
||||
MemoryBuffer::getFileOrSTDIN(Opts.InputFile);
|
||||
MemoryBuffer::getFileOrSTDIN(Opts.InputFile, /*IsText=*/true);
|
||||
|
||||
if (std::error_code EC = Buffer.getError()) {
|
||||
Error = EC.message();
|
||||
@@ -277,11 +275,15 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
if (!Opts.SplitDwarfOutput.empty())
|
||||
DwoOS = getOutputStream(Opts.SplitDwarfOutput, Diags, IsBinary);
|
||||
|
||||
// FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
|
||||
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
|
||||
std::unique_ptr<MCObjectFileInfo> MOFI(new MCObjectFileInfo());
|
||||
// Build up the feature string from the target feature list.
|
||||
std::string FS = llvm::join(Opts.Features, ",");
|
||||
|
||||
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr, &MCOptions);
|
||||
std::unique_ptr<MCSubtargetInfo> STI(
|
||||
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
|
||||
assert(STI && "Unable to create subtarget info!");
|
||||
|
||||
MCContext Ctx(Triple(Opts.Triple), MAI.get(), MRI.get(), STI.get(), &SrcMgr,
|
||||
&MCOptions);
|
||||
|
||||
bool PIC = false;
|
||||
if (Opts.RelocationModel == "static") {
|
||||
@@ -294,7 +296,12 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
PIC = false;
|
||||
}
|
||||
|
||||
MOFI->InitMCObjectFileInfo(Triple(Opts.Triple), PIC, Ctx);
|
||||
// FIXME: This is not pretty. MCContext has a ptr to MCObjectFileInfo and
|
||||
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
|
||||
std::unique_ptr<MCObjectFileInfo> MOFI(
|
||||
TheTarget->createMCObjectFileInfo(Ctx, PIC));
|
||||
Ctx.setObjectFileInfo(MOFI.get());
|
||||
|
||||
if (Opts.SaveTemporaryLabels)
|
||||
Ctx.setAllowTemporaryLabels(false);
|
||||
if (Opts.GenDwarfForAssembly)
|
||||
@@ -316,19 +323,16 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
Ctx.addDebugPrefixMapEntry(KV.first, KV.second);
|
||||
if (!Opts.MainFileName.empty())
|
||||
Ctx.setMainFileName(StringRef(Opts.MainFileName));
|
||||
Ctx.setDwarfFormat(Opts.Dwarf64 ? dwarf::DWARF64 : dwarf::DWARF32);
|
||||
Ctx.setDwarfVersion(Opts.DwarfVersion);
|
||||
if (Opts.GenDwarfForAssembly)
|
||||
Ctx.setGenDwarfRootFile(Opts.InputFile,
|
||||
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
|
||||
|
||||
// Build up the feature string from the target feature list.
|
||||
std::string FS = llvm::join(Opts.Features, ",");
|
||||
|
||||
std::unique_ptr<MCStreamer> Str;
|
||||
|
||||
std::unique_ptr<MCInstrInfo> MCII(TheTarget->createMCInstrInfo());
|
||||
std::unique_ptr<MCSubtargetInfo> STI(
|
||||
TheTarget->createMCSubtargetInfo(Opts.Triple, Opts.CPU, FS));
|
||||
assert(MCII && "Unable to create instruction info!");
|
||||
|
||||
raw_pwrite_stream *Out = FDOS.get();
|
||||
std::unique_ptr<buffer_ostream> BOS;
|
||||
@@ -367,6 +371,8 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
TheTarget->createMCCodeEmitter(*MCII, *MRI, Ctx));
|
||||
std::unique_ptr<MCAsmBackend> MAB(
|
||||
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
||||
assert(MAB && "Unable to create asm backend!");
|
||||
|
||||
std::unique_ptr<MCObjectWriter> OW =
|
||||
DwoOS ? MAB->createDwoObjectWriter(*Out, *DwoOS)
|
||||
: MAB->createObjectWriter(*Out);
|
||||
@@ -381,8 +387,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
|
||||
// When -fembed-bitcode is passed to clang_as, a 1-byte marker
|
||||
// is emitted in __LLVM,__asm section if the object file is MachO format.
|
||||
if (Opts.EmbedBitcode && Ctx.getObjectFileInfo()->getObjectFileType() ==
|
||||
MCObjectFileInfo::IsMachO) {
|
||||
if (Opts.EmbedBitcode && Ctx.getObjectFileType() == MCContext::IsMachO) {
|
||||
MCSection *AsmLabel = Ctx.getMachOSection(
|
||||
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
|
||||
Str.get()->SwitchSection(AsmLabel);
|
||||
@@ -419,12 +424,12 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
Failed = Parser->Run(Opts.NoInitialTextSection);
|
||||
}
|
||||
|
||||
// Close Streamer first.
|
||||
// It might have a reference to the output stream.
|
||||
Str.reset();
|
||||
// Close the output stream early.
|
||||
BOS.reset();
|
||||
FDOS.reset();
|
||||
return Failed;
|
||||
}
|
||||
|
||||
bool ExecuteAssembler(AssemblerInvocation &Opts,
|
||||
DiagnosticsEngine &Diags) {
|
||||
bool Failed = ExecuteAssemblerImpl(Opts, Diags);
|
||||
|
||||
// Delete output file if there were errors.
|
||||
if (Failed) {
|
||||
@@ -472,7 +477,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
||||
return 1;
|
||||
|
||||
if (Asm.ShowHelp) {
|
||||
getDriverOptTable().PrintHelp(
|
||||
getDriverOptTable().printHelp(
|
||||
llvm::outs(), "clang -cc1as [options] file...",
|
||||
"Clang Integrated Assembler",
|
||||
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
|
||||
|
||||
@@ -39,6 +39,7 @@ struct AssemblerInvocation {
|
||||
unsigned SaveTemporaryLabels : 1;
|
||||
unsigned GenDwarfForAssembly : 1;
|
||||
unsigned RelaxELFRelocations : 1;
|
||||
unsigned Dwarf64 : 1;
|
||||
unsigned DwarfVersion;
|
||||
std::string DwarfDebugFlags;
|
||||
std::string DwarfDebugProducer;
|
||||
@@ -108,6 +109,7 @@ public:
|
||||
FatalWarnings = 0;
|
||||
NoWarn = 0;
|
||||
IncrementalLinkerCompatible = 0;
|
||||
Dwarf64 = 0;
|
||||
DwarfVersion = 0;
|
||||
EmbedBitcode = 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSplitDepFile(t *testing.T) {
|
||||
for i, tc := range []struct {
|
||||
in string
|
||||
out []string
|
||||
}{
|
||||
{`deps: foo bar`, []string{"foo", "bar"}},
|
||||
{`deps: foo "bar"`, []string{"foo", "bar"}},
|
||||
{`deps: "foo" bar`, []string{"foo", "bar"}},
|
||||
{`deps: "foo bar"`, []string{"foo bar"}},
|
||||
{`deps: "foo bar" `, []string{"foo bar"}},
|
||||
{"deps: foo\nbar", []string{"foo"}},
|
||||
{"deps: foo \\\nbar", []string{"foo", "bar"}},
|
||||
{"deps: foo\\bar \\\nbaz", []string{"foo\\bar", "baz"}},
|
||||
{"deps: foo\\bar \\\r\n baz", []string{"foo\\bar", "baz"}}, // Windows uses CRLF line endings
|
||||
} {
|
||||
out, err := parseDepFile(tc.in)
|
||||
if err != nil {
|
||||
t.Errorf("test #%d failed: %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(out, tc.out) {
|
||||
t.Errorf("test #%d failed: expected %#v but got %#v", i, tc.out, out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-12
@@ -2,6 +2,7 @@ package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
@@ -21,13 +22,24 @@ func init() {
|
||||
commands["clang"] = []string{"clang-" + llvmMajor}
|
||||
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
|
||||
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
|
||||
commands["lldb"] = []string{"lldb-" + llvmMajor, "lldb"}
|
||||
// Add the path to a Homebrew-installed LLVM for ease of use (no need to
|
||||
// manually set $PATH).
|
||||
if runtime.GOOS == "darwin" {
|
||||
prefix := "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
|
||||
var prefix string
|
||||
switch runtime.GOARCH {
|
||||
case "amd64":
|
||||
prefix = "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
|
||||
case "arm64":
|
||||
prefix = "/opt/homebrew/opt/llvm@" + llvmMajor + "/bin/"
|
||||
default:
|
||||
// unknown GOARCH
|
||||
panic(fmt.Sprintf("unknown GOARCH: %s on darwin", runtime.GOARCH))
|
||||
}
|
||||
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
||||
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
||||
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
|
||||
}
|
||||
// Add the path for when LLVM was installed with the installer from
|
||||
// llvm.org, which by default doesn't add LLVM to the $PATH environment
|
||||
@@ -36,6 +48,7 @@ func init() {
|
||||
commands["clang"] = append(commands["clang"], "clang", "C:\\Program Files\\LLVM\\bin\\clang.exe")
|
||||
commands["ld.lld"] = append(commands["ld.lld"], "lld", "C:\\Program Files\\LLVM\\bin\\lld.exe")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], "C:\\Program Files\\LLVM\\bin\\wasm-ld.exe")
|
||||
commands["lldb"] = append(commands["lldb"], "C:\\Program Files\\LLVM\\bin\\lldb.exe")
|
||||
}
|
||||
// Add the path to LLVM installed from ports.
|
||||
if runtime.GOOS == "freebsd" {
|
||||
@@ -43,23 +56,34 @@ func init() {
|
||||
commands["clang"] = append(commands["clang"], prefix+"clang-"+llvmMajor)
|
||||
commands["ld.lld"] = append(commands["ld.lld"], prefix+"ld.lld")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], prefix+"wasm-ld")
|
||||
commands["lldb"] = append(commands["lldb"], prefix+"lldb")
|
||||
}
|
||||
}
|
||||
|
||||
func execCommand(cmdNames []string, args ...string) error {
|
||||
for _, cmdName := range cmdNames {
|
||||
cmd := exec.Command(cmdName, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
// LookupCommand looks up the executable name for a given LLVM tool such as
|
||||
// clang or wasm-ld. It returns the (relative) command that can be used to
|
||||
// invoke the tool or an error if it could not be found.
|
||||
func LookupCommand(name string) (string, error) {
|
||||
for _, cmdName := range commands[name] {
|
||||
_, err := exec.LookPath(cmdName)
|
||||
if err != nil {
|
||||
if err, ok := err.(*exec.Error); ok && (err.Err == exec.ErrNotFound || err.Err.Error() == "file does not exist") {
|
||||
// this command was not found, try the next
|
||||
if errors.Unwrap(err) == exec.ErrNotFound {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
return cmdName, err
|
||||
}
|
||||
return nil
|
||||
return cmdName, nil
|
||||
}
|
||||
return errors.New("none of these commands were found in your $PATH: " + strings.Join(cmdNames, " "))
|
||||
return "", errors.New("%#v: none of these commands were found in your $PATH: " + strings.Join(commands[name], " "))
|
||||
}
|
||||
|
||||
func execCommand(name string, args ...string) error {
|
||||
name, err := LookupCommand(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd := exec.Command(name, args...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
+13
-3
@@ -13,22 +13,32 @@ import (
|
||||
// uses the currently active GOPATH (from the goenv package) to determine the Go
|
||||
// version to use.
|
||||
func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
spec, err := compileopts.LoadTarget(options.Target)
|
||||
spec, err := compileopts.LoadTarget(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.OpenOCDCommands != nil {
|
||||
// Override the OpenOCDCommands from the target spec if specified on
|
||||
// the command-line
|
||||
spec.OpenOCDCommands = options.OpenOCDCommands
|
||||
}
|
||||
|
||||
goroot := goenv.Get("GOROOT")
|
||||
if goroot == "" {
|
||||
return nil, errors.New("cannot locate $GOROOT, please set it manually")
|
||||
}
|
||||
|
||||
major, minor, err := goenv.GetGorootVersion(goroot)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read version from GOROOT (%v): %v", goroot, err)
|
||||
}
|
||||
if major != 1 || minor < 13 || minor > 16 {
|
||||
return nil, fmt.Errorf("requires go version 1.13 through 1.16, got go%d.%d", major, minor)
|
||||
if major != 1 || minor < 15 || minor > 17 {
|
||||
return nil, fmt.Errorf("requires go version 1.15 through 1.17, got go%d.%d", major, minor)
|
||||
}
|
||||
|
||||
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
|
||||
return &compileopts.Config{
|
||||
Options: options,
|
||||
Target: spec,
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func getElfSectionData(executable string, sectionName string) ([]byte, elf.FileHeader, error) {
|
||||
elfFile, err := elf.Open(executable)
|
||||
if err != nil {
|
||||
return nil, elf.FileHeader{}, err
|
||||
}
|
||||
defer elfFile.Close()
|
||||
|
||||
section := elfFile.Section(sectionName)
|
||||
if section == nil {
|
||||
return nil, elf.FileHeader{}, fmt.Errorf("could not find %s section", sectionName)
|
||||
}
|
||||
|
||||
data, err := section.Data()
|
||||
|
||||
return data, elfFile.FileHeader, err
|
||||
}
|
||||
|
||||
func replaceElfSection(executable string, sectionName string, data []byte) error {
|
||||
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
elfFile, err := elf.Open(executable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer elfFile.Close()
|
||||
|
||||
section := elfFile.Section(sectionName)
|
||||
if section == nil {
|
||||
return fmt.Errorf("could not find %s section", sectionName)
|
||||
}
|
||||
|
||||
// Implicitly check for compressed sections
|
||||
if section.Size != section.FileSize {
|
||||
return fmt.Errorf("expected section %s to have identical size and file size, got %d and %d", sectionName, section.Size, section.FileSize)
|
||||
}
|
||||
|
||||
// Only permit complete replacement of section
|
||||
if section.Size != uint64(len(data)) {
|
||||
return fmt.Errorf("expected section %s to have size %d, was actually %d", sectionName, len(data), section.Size)
|
||||
}
|
||||
|
||||
// Write the replacement section data
|
||||
_, err = fp.WriteAt(data, int64(section.Offset))
|
||||
return err
|
||||
}
|
||||
@@ -84,6 +84,20 @@ func getClangHeaderPath(TINYGOROOT string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// On Arch Linux, the clang executable is stored in /usr/bin rather than being symlinked from there.
|
||||
// Search directly in /usr/lib for clang.
|
||||
if matches, err := filepath.Glob("/usr/lib/clang/" + llvmMajor + ".*.*"); err == nil {
|
||||
// Check for the highest version first.
|
||||
sort.Strings(matches)
|
||||
for i := len(matches) - 1; i >= 0; i-- {
|
||||
path := filepath.Join(matches[i], "include")
|
||||
_, err := os.Stat(filepath.Join(path, "stdint.h"))
|
||||
if err == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Could not find it.
|
||||
return ""
|
||||
}
|
||||
|
||||
+19
-6
@@ -78,11 +78,21 @@ func makeESPFirmareImage(infile, outfile, format string) error {
|
||||
// An added benefit is that we don't need to check for errors all the time.
|
||||
outf := &bytes.Buffer{}
|
||||
|
||||
// Chip IDs. Source:
|
||||
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L22
|
||||
chip_id := map[string]uint16{
|
||||
"esp32": 0x0000,
|
||||
"esp32c3": 0x0005,
|
||||
}[format]
|
||||
|
||||
// Image header.
|
||||
switch format {
|
||||
case "esp32":
|
||||
case "esp32", "esp32c3":
|
||||
// Header format:
|
||||
// https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
|
||||
// https://github.com/espressif/esp-idf/blob/v4.3/components/bootloader_support/include/esp_app_format.h#L71
|
||||
// Note: not adding a SHA256 hash as the binary is modified by
|
||||
// esptool.py while flashing and therefore the hash won't be valid
|
||||
// anymore.
|
||||
binary.Write(outf, binary.LittleEndian, struct {
|
||||
magic uint8
|
||||
segment_count uint8
|
||||
@@ -91,15 +101,18 @@ func makeESPFirmareImage(infile, outfile, format string) error {
|
||||
entry_addr uint32
|
||||
wp_pin uint8
|
||||
spi_pin_drv [3]uint8
|
||||
reserved [11]uint8
|
||||
chip_id uint16
|
||||
min_chip_rev uint8
|
||||
reserved [8]uint8
|
||||
hash_appended bool
|
||||
}{
|
||||
magic: 0xE9,
|
||||
segment_count: byte(len(segments)),
|
||||
spi_mode: 0, // irrelevant, replaced by esptool when flashing
|
||||
spi_speed_size: 0, // spi_speed, spi_size: replaced by esptool when flashing
|
||||
spi_mode: 2, // ESP_IMAGE_SPI_MODE_DIO
|
||||
spi_speed_size: 0x1f, // ESP_IMAGE_SPI_SPEED_80M, ESP_IMAGE_FLASH_SIZE_2MB
|
||||
entry_addr: uint32(inf.Entry),
|
||||
wp_pin: 0xEE, // disable WP pin
|
||||
chip_id: chip_id,
|
||||
hash_appended: true, // add a SHA256 hash
|
||||
})
|
||||
case "esp8266":
|
||||
@@ -142,7 +155,7 @@ func makeESPFirmareImage(infile, outfile, format string) error {
|
||||
outf.Write(make([]byte, 15-outf.Len()%16))
|
||||
outf.WriteByte(checksum)
|
||||
|
||||
if format == "esp32" {
|
||||
if format != "esp8266" {
|
||||
// SHA256 hash (to protect against image corruption, not for security).
|
||||
hash := sha256.Sum256(outf.Bytes())
|
||||
outf.Write(hash[:])
|
||||
|
||||
+148
-79
@@ -4,8 +4,12 @@ package builder
|
||||
// parallel while taking care of dependencies.
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -27,97 +31,139 @@ const (
|
||||
type compileJob struct {
|
||||
description string // description, only used for logging
|
||||
dependencies []*compileJob
|
||||
run func() error
|
||||
state jobState
|
||||
result string // result (path)
|
||||
run func(*compileJob) (err error)
|
||||
err error // error if finished
|
||||
duration time.Duration // how long it took to run this job (only set after finishing)
|
||||
}
|
||||
|
||||
// readyToRun returns whether this job is ready to run: it is itself not yet
|
||||
// started and all dependencies are finished.
|
||||
func (job *compileJob) readyToRun() bool {
|
||||
if job.state != jobStateQueued {
|
||||
// Already running or finished, so shouldn't be run again.
|
||||
return false
|
||||
// dummyCompileJob returns a new *compileJob that produces an output without
|
||||
// doing anything. This can be useful where a *compileJob producing an output is
|
||||
// expected but nothing needs to be done, for example for a load from a cache.
|
||||
func dummyCompileJob(result string) *compileJob {
|
||||
return &compileJob{
|
||||
description: "<dummy>",
|
||||
result: result,
|
||||
}
|
||||
|
||||
// Check dependencies.
|
||||
for _, dep := range job.dependencies {
|
||||
if dep.state != jobStateFinished {
|
||||
// A dependency is not finished, so this job has to wait until it
|
||||
// is.
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// All conditions are satisfied.
|
||||
return true
|
||||
}
|
||||
|
||||
// runJobs runs all the jobs indicated in the jobs slice and returns the error
|
||||
// of the first job that fails to run.
|
||||
// It runs all jobs in the order of the slice, as long as all dependencies have
|
||||
// already run. Therefore, if some jobs are preferred to run before others, they
|
||||
// should be ordered as such in this slice.
|
||||
func runJobs(jobs []*compileJob) error {
|
||||
// Create channels to communicate with the workers.
|
||||
doneChan := make(chan *compileJob)
|
||||
workerChan := make(chan *compileJob)
|
||||
defer close(workerChan)
|
||||
|
||||
// Start a number of workers.
|
||||
for i := 0; i < runtime.NumCPU(); i++ {
|
||||
if jobRunnerDebug {
|
||||
fmt.Println("## starting worker", i)
|
||||
}
|
||||
go jobWorker(workerChan, doneChan)
|
||||
// runJobs runs the indicated job and all its dependencies. For every job, all
|
||||
// the dependencies are run first. It returns the error of the first job that
|
||||
// fails.
|
||||
// It runs all jobs in the order of the dependencies slice, depth-first.
|
||||
// Therefore, if some jobs are preferred to run before others, they should be
|
||||
// ordered as such in the job dependencies.
|
||||
func runJobs(job *compileJob, sema chan struct{}) error {
|
||||
if sema == nil {
|
||||
// Have a default, if the semaphore isn't set. This is useful for
|
||||
// tests.
|
||||
sema = make(chan struct{}, runtime.NumCPU())
|
||||
}
|
||||
if cap(sema) == 0 {
|
||||
return errors.New("cannot 0 jobs at a time")
|
||||
}
|
||||
|
||||
// Create a slice of jobs to run, where all dependencies are run in order.
|
||||
jobs := []*compileJob{}
|
||||
addedJobs := map[*compileJob]struct{}{}
|
||||
var addJobs func(*compileJob)
|
||||
addJobs = func(job *compileJob) {
|
||||
if _, ok := addedJobs[job]; ok {
|
||||
return
|
||||
}
|
||||
for _, dep := range job.dependencies {
|
||||
addJobs(dep)
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
addedJobs[job] = struct{}{}
|
||||
}
|
||||
addJobs(job)
|
||||
|
||||
waiting := make(map[*compileJob]map[*compileJob]struct{}, len(jobs))
|
||||
dependents := make(map[*compileJob][]*compileJob, len(jobs))
|
||||
jidx := make(map[*compileJob]int)
|
||||
var ready intHeap
|
||||
for i, job := range jobs {
|
||||
jidx[job] = i
|
||||
if len(job.dependencies) == 0 {
|
||||
// This job is ready to run.
|
||||
ready.Push(i)
|
||||
continue
|
||||
}
|
||||
|
||||
// Construct a map for dependencies which the job is currently waiting on.
|
||||
waitDeps := make(map[*compileJob]struct{})
|
||||
waiting[job] = waitDeps
|
||||
|
||||
// Add the job to the dependents list of each dependency.
|
||||
for _, dep := range job.dependencies {
|
||||
dependents[dep] = append(dependents[dep], job)
|
||||
waitDeps[dep] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a channel to accept notifications of completion.
|
||||
doneChan := make(chan *compileJob)
|
||||
|
||||
// Send each job in the jobs slice to a worker, taking care of job
|
||||
// dependencies.
|
||||
numRunningJobs := 0
|
||||
var totalTime time.Duration
|
||||
start := time.Now()
|
||||
for {
|
||||
// If there are free workers, try starting a new job (if one is
|
||||
// available). If it succeeds, try again to fill the entire worker pool.
|
||||
if numRunningJobs < runtime.NumCPU() {
|
||||
jobToRun := nextJob(jobs)
|
||||
if jobToRun != nil {
|
||||
// Start job.
|
||||
for len(ready.IntSlice) > 0 || numRunningJobs != 0 {
|
||||
var completed *compileJob
|
||||
if len(ready.IntSlice) > 0 {
|
||||
select {
|
||||
case sema <- struct{}{}:
|
||||
// Start a job.
|
||||
job := jobs[heap.Pop(&ready).(int)]
|
||||
if jobRunnerDebug {
|
||||
fmt.Println("## start: ", jobToRun.description)
|
||||
fmt.Println("## start: ", job.description)
|
||||
}
|
||||
jobToRun.state = jobStateRunning
|
||||
workerChan <- jobToRun
|
||||
go runJob(job, doneChan)
|
||||
numRunningJobs++
|
||||
continue
|
||||
|
||||
case completed = <-doneChan:
|
||||
// A job completed.
|
||||
}
|
||||
} else {
|
||||
// Wait for a job to complete.
|
||||
completed = <-doneChan
|
||||
}
|
||||
|
||||
// When there are no jobs running, all jobs in the jobs slice must have
|
||||
// been finished. Therefore, the work is done.
|
||||
if numRunningJobs == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
// Wait until a job is finished.
|
||||
job := <-doneChan
|
||||
job.state = jobStateFinished
|
||||
numRunningJobs--
|
||||
totalTime += job.duration
|
||||
<-sema
|
||||
if jobRunnerDebug {
|
||||
fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
|
||||
}
|
||||
if job.err != nil {
|
||||
// Wait for running jobs to finish.
|
||||
if completed.err != nil {
|
||||
// Wait for any current jobs to finish.
|
||||
for numRunningJobs != 0 {
|
||||
<-doneChan
|
||||
numRunningJobs--
|
||||
}
|
||||
// Return error of first failing job.
|
||||
return job.err
|
||||
|
||||
// The build failed.
|
||||
return completed.err
|
||||
}
|
||||
|
||||
// Update total run time.
|
||||
totalTime += completed.duration
|
||||
|
||||
// Update dependent jobs.
|
||||
for _, j := range dependents[completed] {
|
||||
wait := waiting[j]
|
||||
delete(wait, completed)
|
||||
if len(wait) == 0 {
|
||||
// This job is now ready to run.
|
||||
ready.Push(jidx[j])
|
||||
delete(waiting, j)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(waiting) != 0 {
|
||||
// There is a dependency cycle preventing some jobs from running.
|
||||
return errDependencyCycle{waiting}
|
||||
}
|
||||
|
||||
// Some statistics, if debugging.
|
||||
@@ -134,27 +180,50 @@ func runJobs(jobs []*compileJob) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// nextJob returns the first ready-to-run job.
|
||||
// This is an implementation detail of runJobs.
|
||||
func nextJob(jobs []*compileJob) *compileJob {
|
||||
for _, job := range jobs {
|
||||
if job.readyToRun() {
|
||||
return job
|
||||
}
|
||||
}
|
||||
return nil
|
||||
type errDependencyCycle struct {
|
||||
waiting map[*compileJob]map[*compileJob]struct{}
|
||||
}
|
||||
|
||||
// jobWorker is the goroutine that runs received jobs.
|
||||
// This is an implementation detail of runJobs.
|
||||
func jobWorker(workerChan, doneChan chan *compileJob) {
|
||||
for job := range workerChan {
|
||||
start := time.Now()
|
||||
err := job.run()
|
||||
func (err errDependencyCycle) Error() string {
|
||||
waits := make([]string, 0, len(err.waiting))
|
||||
for j, wait := range err.waiting {
|
||||
deps := make([]string, 0, len(wait))
|
||||
for dep := range wait {
|
||||
deps = append(deps, dep.description)
|
||||
}
|
||||
sort.Strings(deps)
|
||||
|
||||
waits = append(waits, fmt.Sprintf("\t%s is waiting for [%s]",
|
||||
j.description, strings.Join(deps, ", "),
|
||||
))
|
||||
}
|
||||
sort.Strings(waits)
|
||||
return "deadlock:\n" + strings.Join(waits, "\n")
|
||||
}
|
||||
|
||||
type intHeap struct {
|
||||
sort.IntSlice
|
||||
}
|
||||
|
||||
func (h *intHeap) Push(x interface{}) {
|
||||
h.IntSlice = append(h.IntSlice, x.(int))
|
||||
}
|
||||
|
||||
func (h *intHeap) Pop() interface{} {
|
||||
x := h.IntSlice[len(h.IntSlice)-1]
|
||||
h.IntSlice = h.IntSlice[:len(h.IntSlice)-1]
|
||||
return x
|
||||
}
|
||||
|
||||
// runJob runs a compile job and notifies doneChan of completion.
|
||||
func runJob(job *compileJob, doneChan chan *compileJob) {
|
||||
start := time.Now()
|
||||
if job.run != nil {
|
||||
err := job.run(job)
|
||||
if err != nil {
|
||||
job.err = err
|
||||
}
|
||||
job.duration = time.Since(start)
|
||||
doneChan <- job
|
||||
}
|
||||
job.duration = time.Since(start)
|
||||
doneChan <- job
|
||||
}
|
||||
|
||||
+156
-59
@@ -1,10 +1,14 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
@@ -14,91 +18,138 @@ type Library struct {
|
||||
// The library name, such as compiler-rt or picolibc.
|
||||
name string
|
||||
|
||||
cflags func() []string
|
||||
// makeHeaders creates a header include dir for the library
|
||||
makeHeaders func(target, includeDir string) error
|
||||
|
||||
// cflags returns the C flags specific to this library
|
||||
cflags func(target, headerPath string) []string
|
||||
|
||||
// The source directory, relative to TINYGOROOT.
|
||||
sourceDir string
|
||||
|
||||
// The source files, relative to sourceDir.
|
||||
sources func(target string) []string
|
||||
}
|
||||
librarySources func(target string) []string
|
||||
|
||||
// fullPath returns the full path to the source directory.
|
||||
func (l *Library) fullPath() string {
|
||||
return filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir)
|
||||
}
|
||||
|
||||
// sourcePaths returns a slice with the full paths to the source files.
|
||||
func (l *Library) sourcePaths(target string) []string {
|
||||
sources := l.sources(target)
|
||||
paths := make([]string, len(sources))
|
||||
for i, name := range sources {
|
||||
paths[i] = filepath.Join(l.fullPath(), name)
|
||||
}
|
||||
return paths
|
||||
// The source code for the crt1.o file, relative to sourceDir.
|
||||
crt1Source string
|
||||
}
|
||||
|
||||
// Load the library archive, possibly generating and caching it if needed.
|
||||
// The resulting file is stored in the provided tmpdir, which is expected to be
|
||||
// removed after the Load call.
|
||||
func (l *Library) Load(target, tmpdir string) (path string, err error) {
|
||||
path, job, err := l.load(target, "", tmpdir)
|
||||
// The resulting directory may be stored in the provided tmpdir, which is
|
||||
// expected to be removed after the Load call.
|
||||
func (l *Library) Load(config *compileopts.Config, tmpdir string) (dir string, err error) {
|
||||
job, unlock, err := l.load(config, tmpdir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if job != nil {
|
||||
jobs := append([]*compileJob{job}, job.dependencies...)
|
||||
err = runJobs(jobs)
|
||||
}
|
||||
return path, err
|
||||
defer unlock()
|
||||
err = runJobs(job, config.Options.Semaphore)
|
||||
return filepath.Dir(job.result), err
|
||||
}
|
||||
|
||||
// load returns a path to the library file for the given target, loading it from
|
||||
// cache if possible. It will return a non-zero compiler job if the library
|
||||
// wasn't cached, this job (and its dependencies) must be run before the library
|
||||
// path is valid.
|
||||
// load returns a compile job to build this library file for the given target
|
||||
// and CPU. It may return a dummy compileJob if the library build is already
|
||||
// cached. The path is stored as job.result but is only valid after the job has
|
||||
// been run.
|
||||
// The provided tmpdir will be used to store intermediary files and possibly the
|
||||
// output archive file, it is expected to be removed after use.
|
||||
func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob, err error) {
|
||||
// Try to load a precompiled library.
|
||||
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, l.name+".a")
|
||||
if _, err := os.Stat(precompiledPath); err == nil {
|
||||
// As a side effect, this call creates the library header files if they didn't
|
||||
// exist yet.
|
||||
func (l *Library) load(config *compileopts.Config, tmpdir string) (job *compileJob, abortLock func(), err error) {
|
||||
outdir, precompiled := config.LibcPath(l.name)
|
||||
archiveFilePath := filepath.Join(outdir, "lib.a")
|
||||
if precompiled {
|
||||
// Found a precompiled library for this OS/architecture. Return the path
|
||||
// directly.
|
||||
return precompiledPath, nil, nil
|
||||
return dummyCompileJob(archiveFilePath), func() {}, nil
|
||||
}
|
||||
|
||||
var outfile string
|
||||
if cpu != "" {
|
||||
outfile = l.name + "-" + target + "-" + cpu + ".a"
|
||||
} else {
|
||||
outfile = l.name + "-" + target + ".a"
|
||||
}
|
||||
// Create a lock on the output (if supported).
|
||||
// This is a bit messy, but avoids a deadlock because it is ordered consistently with other library loads within a build.
|
||||
outname := filepath.Base(outdir)
|
||||
unlock := lock(filepath.Join(goenv.Get("GOCACHE"), outname+".lock"))
|
||||
var ok bool
|
||||
defer func() {
|
||||
if !ok {
|
||||
unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
// Try to fetch this library from the cache.
|
||||
if path, err := cacheLoad(outfile, l.sourcePaths(target)); path != "" || err != nil {
|
||||
// Cache hit.
|
||||
return path, nil, err
|
||||
if _, err := os.Stat(archiveFilePath); err == nil {
|
||||
return dummyCompileJob(archiveFilePath), func() {}, nil
|
||||
}
|
||||
// Cache miss, build it now.
|
||||
|
||||
// Create the destination directory where the components of this library
|
||||
// (lib.a file, include directory) are placed.
|
||||
err = os.MkdirAll(filepath.Join(goenv.Get("GOCACHE"), outname), 0o777)
|
||||
if err != nil {
|
||||
// Could not create directory (and not because it already exists).
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Make headers if needed.
|
||||
headerPath := filepath.Join(outdir, "include")
|
||||
target := config.Triple()
|
||||
if l.makeHeaders != nil {
|
||||
if _, err = os.Stat(headerPath); err != nil {
|
||||
temporaryHeaderPath, err := ioutil.TempDir(outdir, "include.tmp*")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer os.RemoveAll(temporaryHeaderPath)
|
||||
err = l.makeHeaders(target, temporaryHeaderPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = os.Rename(temporaryHeaderPath, headerPath)
|
||||
if err != nil {
|
||||
switch {
|
||||
case os.IsExist(err):
|
||||
// Another invocation of TinyGo also seems to have already created the headers.
|
||||
|
||||
case runtime.GOOS == "windows" && os.IsPermission(err):
|
||||
// On Windows, a rename with a destination directory that already
|
||||
// exists does not result in an IsExist error, but rather in an
|
||||
// access denied error. To be sure, check for this case by checking
|
||||
// whether the target directory exists.
|
||||
if _, err := os.Stat(headerPath); err == nil {
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
default:
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
remapDir := filepath.Join(os.TempDir(), "tinygo-"+l.name)
|
||||
dir := filepath.Join(tmpdir, "build-lib-"+l.name)
|
||||
err = os.Mkdir(dir, 0777)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Precalculate the flags to the compiler invocation.
|
||||
// Note: -fdebug-prefix-map is necessary to make the output archive
|
||||
// reproducible. Otherwise the temporary directory is stored in the archive
|
||||
// itself, which varies each run.
|
||||
args := append(l.cflags(), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
|
||||
args := append(l.cflags(target, headerPath), "-c", "-Oz", "-g", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target="+target, "-fdebug-prefix-map="+dir+"="+remapDir)
|
||||
cpu := config.CPU()
|
||||
if cpu != "" {
|
||||
args = append(args, "-mcpu="+cpu)
|
||||
// X86 has deprecated the -mcpu flag, so we need to use -march instead.
|
||||
// However, ARM has not done this.
|
||||
if strings.HasPrefix(target, "i386") || strings.HasPrefix(target, "x86_64") {
|
||||
args = append(args, "-march="+cpu)
|
||||
} else {
|
||||
args = append(args, "-mcpu="+cpu)
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
||||
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft")
|
||||
args = append(args, "-fshort-enums", "-fomit-frame-pointer", "-mfloat-abi=soft", "-fno-unwind-tables")
|
||||
}
|
||||
if strings.HasPrefix(target, "riscv32-") {
|
||||
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
|
||||
@@ -107,37 +158,54 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
|
||||
args = append(args, "-march=rv64gc", "-mabi=lp64")
|
||||
}
|
||||
|
||||
var once sync.Once
|
||||
|
||||
// Create job to put all the object files in a single archive. This archive
|
||||
// file is the (static) library file.
|
||||
var objs []string
|
||||
arpath := filepath.Join(dir, l.name+".a")
|
||||
job = &compileJob{
|
||||
description: "ar " + l.name + ".a",
|
||||
run: func() error {
|
||||
description: "ar " + l.name + "/lib.a",
|
||||
result: filepath.Join(goenv.Get("GOCACHE"), outname, "lib.a"),
|
||||
run: func(*compileJob) error {
|
||||
defer once.Do(unlock)
|
||||
|
||||
// Create an archive of all object files.
|
||||
err := makeArchive(arpath, objs)
|
||||
f, err := ioutil.TempFile(outdir, "libc.a.tmp*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = makeArchive(f, objs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Store this archive in the cache.
|
||||
_, err = cacheStore(arpath, outfile, l.sourcePaths(target))
|
||||
return err
|
||||
return os.Rename(f.Name(), archiveFilePath)
|
||||
},
|
||||
}
|
||||
|
||||
// Create jobs to compile all sources. These jobs are depended upon by the
|
||||
// archive job above, so must be run first.
|
||||
for _, srcpath := range l.sourcePaths(target) {
|
||||
srcpath := srcpath // avoid concurrency issues by redefining inside the loop
|
||||
objpath := filepath.Join(dir, filepath.Base(srcpath)+".o")
|
||||
for _, path := range l.librarySources(target) {
|
||||
// Strip leading "../" parts off the path.
|
||||
cleanpath := path
|
||||
for strings.HasPrefix(cleanpath, "../") {
|
||||
cleanpath = cleanpath[3:]
|
||||
}
|
||||
srcpath := filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir, path)
|
||||
objpath := filepath.Join(dir, cleanpath+".o")
|
||||
os.MkdirAll(filepath.Dir(objpath), 0o777)
|
||||
objs = append(objs, objpath)
|
||||
job.dependencies = append(job.dependencies, &compileJob{
|
||||
description: "compile " + srcpath,
|
||||
run: func() error {
|
||||
run: func(*compileJob) error {
|
||||
var compileArgs []string
|
||||
compileArgs = append(compileArgs, args...)
|
||||
compileArgs = append(compileArgs, "-o", objpath, srcpath)
|
||||
err := runCCompiler("clang", compileArgs...)
|
||||
err := runCCompiler(compileArgs...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
@@ -146,5 +214,34 @@ func (l *Library) load(target, cpu, tmpdir string) (path string, job *compileJob
|
||||
})
|
||||
}
|
||||
|
||||
return arpath, job, nil
|
||||
// Create crt1.o job, if needed.
|
||||
// Add this as a (fake) dependency to the ar file so it gets compiled.
|
||||
// (It could be done in parallel with creating the ar file, but it probably
|
||||
// won't make much of a difference in speed).
|
||||
if l.crt1Source != "" {
|
||||
srcpath := filepath.Join(goenv.Get("TINYGOROOT"), l.sourceDir, l.crt1Source)
|
||||
job.dependencies = append(job.dependencies, &compileJob{
|
||||
description: "compile " + srcpath,
|
||||
run: func(*compileJob) error {
|
||||
var compileArgs []string
|
||||
compileArgs = append(compileArgs, args...)
|
||||
tmpfile, err := ioutil.TempFile(outdir, "crt1.o.tmp*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpfile.Close()
|
||||
compileArgs = append(compileArgs, "-o", tmpfile.Name(), srcpath)
|
||||
err = runCCompiler(compileArgs...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
return os.Rename(tmpfile.Name(), filepath.Join(outdir, "crt1.o"))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
ok = true
|
||||
return job, func() {
|
||||
once.Do(unlock)
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -11,6 +11,11 @@ bool tinygo_link_elf(int argc, char **argv) {
|
||||
return lld::elf::link(args, false, llvm::outs(), llvm::errs());
|
||||
}
|
||||
|
||||
bool tinygo_link_mingw(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
return lld::mingw::link(args, false, llvm::outs(), llvm::errs());
|
||||
}
|
||||
|
||||
bool tinygo_link_wasm(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
return lld::wasm::link(args, false, llvm::outs(), llvm::errs());
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
var MinGW = Library{
|
||||
name: "mingw-w64",
|
||||
makeHeaders: func(target, includeDir string) error {
|
||||
// copy _mingw.h
|
||||
srcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "mingw-w64")
|
||||
outf, err := os.Create(includeDir + "/_mingw.h")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer outf.Close()
|
||||
inf, err := os.Open(srcDir + "/mingw-w64-headers/crt/_mingw.h.in")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(outf, inf)
|
||||
return err
|
||||
},
|
||||
cflags: func(target, headerPath string) []string {
|
||||
// No flags necessary because there are no files to compile.
|
||||
return nil
|
||||
},
|
||||
librarySources: func(target string) []string {
|
||||
// We only use the UCRT DLL file. No source files necessary.
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
// makeMinGWExtraLibs returns a slice of jobs to import the correct .dll
|
||||
// libraries. This is done by converting input .def files to .lib files which
|
||||
// can then be linked as usual.
|
||||
//
|
||||
// TODO: cache the result. At the moment, it costs a few hundred milliseconds to
|
||||
// compile these files.
|
||||
func makeMinGWExtraLibs(tmpdir string) []*compileJob {
|
||||
var jobs []*compileJob
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
// Normally all the api-ms-win-crt-*.def files are all compiled to a single
|
||||
// .lib file. But to simplify things, we're going to leave them as separate
|
||||
// files.
|
||||
for _, name := range []string{
|
||||
"kernel32.def.in",
|
||||
"api-ms-win-crt-conio-l1-1-0.def",
|
||||
"api-ms-win-crt-convert-l1-1-0.def",
|
||||
"api-ms-win-crt-environment-l1-1-0.def",
|
||||
"api-ms-win-crt-filesystem-l1-1-0.def",
|
||||
"api-ms-win-crt-heap-l1-1-0.def",
|
||||
"api-ms-win-crt-locale-l1-1-0.def",
|
||||
"api-ms-win-crt-math-l1-1-0.def.in",
|
||||
"api-ms-win-crt-multibyte-l1-1-0.def",
|
||||
"api-ms-win-crt-private-l1-1-0.def.in",
|
||||
"api-ms-win-crt-process-l1-1-0.def",
|
||||
"api-ms-win-crt-runtime-l1-1-0.def.in",
|
||||
"api-ms-win-crt-stdio-l1-1-0.def",
|
||||
"api-ms-win-crt-string-l1-1-0.def",
|
||||
"api-ms-win-crt-time-l1-1-0.def",
|
||||
"api-ms-win-crt-utility-l1-1-0.def",
|
||||
} {
|
||||
outpath := filepath.Join(tmpdir, filepath.Base(name)+".lib")
|
||||
inpath := filepath.Join(root, "lib/mingw-w64/mingw-w64-crt/lib-common/"+name)
|
||||
job := &compileJob{
|
||||
description: "create lib file " + inpath,
|
||||
result: outpath,
|
||||
run: func(job *compileJob) error {
|
||||
defpath := inpath
|
||||
if strings.HasSuffix(inpath, ".in") {
|
||||
// .in files need to be preprocessed by a preprocessor (-E)
|
||||
// first.
|
||||
defpath = outpath + ".def"
|
||||
err := runCCompiler("-E", "-x", "c", "-Wp,-w", "-P", "-DDEF_X64", "-o", defpath, inpath, "-I"+goenv.Get("TINYGOROOT")+"/lib/mingw-w64/mingw-w64-crt/def-include/")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return link("ld.lld", "-m", "i386pep", "-o", outpath, defpath)
|
||||
},
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
}
|
||||
return jobs
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
var Musl = Library{
|
||||
name: "musl",
|
||||
makeHeaders: func(target, includeDir string) error {
|
||||
bits := filepath.Join(includeDir, "bits")
|
||||
err := os.Mkdir(bits, 0777)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
arch := compileopts.MuslArchitecture(target)
|
||||
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib", "musl")
|
||||
|
||||
// Create the file alltypes.h.
|
||||
f, err := os.Create(filepath.Join(bits, "alltypes.h"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
infiles := []string{
|
||||
filepath.Join(muslDir, "arch", arch, "bits", "alltypes.h.in"),
|
||||
filepath.Join(muslDir, "include", "alltypes.h.in"),
|
||||
}
|
||||
for _, infile := range infiles {
|
||||
data, err := ioutil.ReadFile(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lines := strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "TYPEDEF ") {
|
||||
matches := regexp.MustCompile(`TYPEDEF (.*) ([^ ]*);`).FindStringSubmatch(line)
|
||||
value := matches[1]
|
||||
name := matches[2]
|
||||
line = fmt.Sprintf("#if defined(__NEED_%s) && !defined(__DEFINED_%s)\ntypedef %s %s;\n#define __DEFINED_%s\n#endif\n", name, name, value, name, name)
|
||||
}
|
||||
if strings.HasPrefix(line, "STRUCT ") {
|
||||
matches := regexp.MustCompile(`STRUCT * ([^ ]*) (.*);`).FindStringSubmatch(line)
|
||||
name := matches[1]
|
||||
value := matches[2]
|
||||
line = fmt.Sprintf("#if defined(__NEED_struct_%s) && !defined(__DEFINED_struct_%s)\nstruct %s %s;\n#define __DEFINED_struct_%s\n#endif\n", name, name, name, value, name)
|
||||
}
|
||||
f.WriteString(line + "\n")
|
||||
}
|
||||
}
|
||||
f.Close()
|
||||
|
||||
// Create the file syscall.h.
|
||||
f, err = os.Create(filepath.Join(bits, "syscall.h"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := ioutil.ReadFile(filepath.Join(muslDir, "arch", arch, "bits", "syscall.h.in"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = f.Write(bytes.ReplaceAll(data, []byte("__NR_"), []byte("SYS_")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Close()
|
||||
|
||||
return nil
|
||||
},
|
||||
cflags: func(target, headerPath string) []string {
|
||||
arch := compileopts.MuslArchitecture(target)
|
||||
muslDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/musl")
|
||||
return []string{
|
||||
"-std=c99", // same as in musl
|
||||
"-D_XOPEN_SOURCE=700", // same as in musl
|
||||
// Musl triggers some warnings and we don't want to show any
|
||||
// warnings while compiling (only errors or silence), so disable
|
||||
// specific warnings that are triggered in musl.
|
||||
"-Werror",
|
||||
"-Wno-logical-op-parentheses",
|
||||
"-Wno-bitwise-op-parentheses",
|
||||
"-Wno-shift-op-parentheses",
|
||||
"-Wno-ignored-attributes",
|
||||
"-Wno-string-plus-int",
|
||||
"-Qunused-arguments",
|
||||
// Select include dirs. Don't include standard library includes
|
||||
// (that would introduce host dependencies and other complications),
|
||||
// but do include all the include directories expected by musl.
|
||||
"-nostdlibinc",
|
||||
"-I" + muslDir + "/arch/" + arch,
|
||||
"-I" + muslDir + "/arch/generic",
|
||||
"-I" + muslDir + "/src/include",
|
||||
"-I" + muslDir + "/src/internal",
|
||||
"-I" + headerPath,
|
||||
"-I" + muslDir + "/include",
|
||||
"-fno-stack-protector",
|
||||
}
|
||||
},
|
||||
sourceDir: "lib/musl/src",
|
||||
librarySources: func(target string) []string {
|
||||
arch := compileopts.MuslArchitecture(target)
|
||||
globs := []string{
|
||||
"env/*.c",
|
||||
"errno/*.c",
|
||||
"exit/*.c",
|
||||
"internal/defsysinfo.c",
|
||||
"internal/libc.c",
|
||||
"internal/syscall_ret.c",
|
||||
"internal/vdso.c",
|
||||
"malloc/*.c",
|
||||
"mman/*.c",
|
||||
"signal/*.c",
|
||||
"stdio/*.c",
|
||||
"string/*.c",
|
||||
"thread/" + arch + "/*.s",
|
||||
"thread/" + arch + "/*.c",
|
||||
"thread/*.c",
|
||||
"time/*.c",
|
||||
"unistd/*.c",
|
||||
}
|
||||
|
||||
var sources []string
|
||||
seenSources := map[string]struct{}{}
|
||||
basepath := goenv.Get("TINYGOROOT") + "/lib/musl/src/"
|
||||
for _, pattern := range globs {
|
||||
matches, err := filepath.Glob(basepath + pattern)
|
||||
if err != nil {
|
||||
// From the documentation:
|
||||
// > Glob ignores file system errors such as I/O errors reading
|
||||
// > directories. The only possible returned error is
|
||||
// > ErrBadPattern, when pattern is malformed.
|
||||
// So the only possible error is when the (statically defined)
|
||||
// pattern is wrong. In other words, a programming bug.
|
||||
panic("could not glob source dirs: " + err.Error())
|
||||
}
|
||||
for _, match := range matches {
|
||||
relpath, err := filepath.Rel(basepath, match)
|
||||
if err != nil {
|
||||
// Not sure if this is even possible.
|
||||
panic(err)
|
||||
}
|
||||
// Make sure architecture specific files override generic files.
|
||||
id := strings.ReplaceAll(relpath, "/"+arch+"/", "/")
|
||||
if _, ok := seenSources[id]; ok {
|
||||
// Already seen this file, skipping this (generic) file.
|
||||
continue
|
||||
}
|
||||
seenSources[id] = struct{}{}
|
||||
sources = append(sources, relpath)
|
||||
}
|
||||
}
|
||||
return sources
|
||||
},
|
||||
crt1Source: "../crt/crt1.c", // lib/musl/crt/crt1.c
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
)
|
||||
|
||||
// https://infocenter.nordicsemi.com/index.jsp?topic=%2Fug_nrfutil%2FUG%2Fnrfutil%2Fnrfutil_intro.html
|
||||
|
||||
func makeDFUFirmwareImage(options *compileopts.Options, infile, outfile string) error {
|
||||
cmdLine := []string{"nrfutil", "pkg", "generate", "--hw-version", "52", "--sd-req", "0x0", "--debug-mode", "--application", infile, outfile}
|
||||
|
||||
if options.PrintCommands != nil {
|
||||
options.PrintCommands(cmdLine[0], cmdLine[1:]...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
|
||||
cmd.Stdout = ioutil.Discard
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not run nrfutil pkg generate: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+121
-3
@@ -1,6 +1,7 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
@@ -10,17 +11,134 @@ import (
|
||||
// based on newlib.
|
||||
var Picolibc = Library{
|
||||
name: "picolibc",
|
||||
cflags: func() []string {
|
||||
makeHeaders: func(target, includeDir string) error {
|
||||
f, err := os.Create(filepath.Join(includeDir, "picolibc.h"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Close()
|
||||
},
|
||||
cflags: func(target, headerPath string) []string {
|
||||
picolibcDir := filepath.Join(goenv.Get("TINYGOROOT"), "lib/picolibc/newlib/libc")
|
||||
return []string{"-Werror", "-Wall", "-std=gnu11", "-D_COMPILING_NEWLIB", "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", picolibcDir + "/include", "-I" + picolibcDir + "/tinystdio", "-I" + goenv.Get("TINYGOROOT") + "/lib/picolibc-include"}
|
||||
return []string{
|
||||
"-Werror",
|
||||
"-Wall",
|
||||
"-std=gnu11",
|
||||
"-D_COMPILING_NEWLIB",
|
||||
"-DHAVE_ALIAS_ATTRIBUTE",
|
||||
"-DTINY_STDIO",
|
||||
"-nostdlibinc",
|
||||
"-isystem", picolibcDir + "/include",
|
||||
"-I" + picolibcDir + "/tinystdio",
|
||||
"-I" + headerPath,
|
||||
}
|
||||
},
|
||||
sourceDir: "lib/picolibc/newlib/libc",
|
||||
sources: func(target string) []string {
|
||||
librarySources: func(target string) []string {
|
||||
return picolibcSources
|
||||
},
|
||||
}
|
||||
|
||||
var picolibcSources = []string{
|
||||
"../../../picolibc-stdio.c",
|
||||
|
||||
"tinystdio/asprintf.c",
|
||||
"tinystdio/atod_engine.c",
|
||||
"tinystdio/atod_ryu.c",
|
||||
"tinystdio/atof_engine.c",
|
||||
"tinystdio/atof_ryu.c",
|
||||
//"tinystdio/atold_engine.c", // have_long_double and not long_double_equals_double
|
||||
"tinystdio/clearerr.c",
|
||||
"tinystdio/compare_exchange.c",
|
||||
"tinystdio/dtoa_data.c",
|
||||
"tinystdio/dtoa_engine.c",
|
||||
"tinystdio/dtoa_ryu.c",
|
||||
"tinystdio/ecvtbuf.c",
|
||||
"tinystdio/ecvt.c",
|
||||
"tinystdio/ecvt_data.c",
|
||||
"tinystdio/ecvtfbuf.c",
|
||||
"tinystdio/ecvtf.c",
|
||||
"tinystdio/ecvtf_data.c",
|
||||
"tinystdio/exchange.c",
|
||||
//"tinystdio/fclose.c", // posix-io
|
||||
"tinystdio/fcvtbuf.c",
|
||||
"tinystdio/fcvt.c",
|
||||
"tinystdio/fcvtfbuf.c",
|
||||
"tinystdio/fcvtf.c",
|
||||
"tinystdio/fdevopen.c",
|
||||
//"tinystdio/fdopen.c", // posix-io
|
||||
"tinystdio/feof.c",
|
||||
"tinystdio/ferror.c",
|
||||
"tinystdio/fflush.c",
|
||||
"tinystdio/fgetc.c",
|
||||
"tinystdio/fgets.c",
|
||||
"tinystdio/fileno.c",
|
||||
"tinystdio/filestrget.c",
|
||||
"tinystdio/filestrputalloc.c",
|
||||
"tinystdio/filestrput.c",
|
||||
//"tinystdio/fopen.c", // posix-io
|
||||
"tinystdio/fprintf.c",
|
||||
"tinystdio/fputc.c",
|
||||
"tinystdio/fputs.c",
|
||||
"tinystdio/fread.c",
|
||||
"tinystdio/fscanf.c",
|
||||
"tinystdio/fseek.c",
|
||||
"tinystdio/ftell.c",
|
||||
"tinystdio/ftoa_data.c",
|
||||
"tinystdio/ftoa_engine.c",
|
||||
"tinystdio/ftoa_ryu.c",
|
||||
"tinystdio/fwrite.c",
|
||||
"tinystdio/gcvtbuf.c",
|
||||
"tinystdio/gcvt.c",
|
||||
"tinystdio/gcvtfbuf.c",
|
||||
"tinystdio/gcvtf.c",
|
||||
"tinystdio/getchar.c",
|
||||
"tinystdio/gets.c",
|
||||
"tinystdio/matchcaseprefix.c",
|
||||
"tinystdio/perror.c",
|
||||
//"tinystdio/posixiob.c", // posix-io
|
||||
//"tinystdio/posixio.c", // posix-io
|
||||
"tinystdio/printf.c",
|
||||
"tinystdio/putchar.c",
|
||||
"tinystdio/puts.c",
|
||||
"tinystdio/ryu_divpow2.c",
|
||||
"tinystdio/ryu_log10.c",
|
||||
"tinystdio/ryu_log2pow5.c",
|
||||
"tinystdio/ryu_pow5bits.c",
|
||||
"tinystdio/ryu_table.c",
|
||||
"tinystdio/ryu_umul128.c",
|
||||
"tinystdio/scanf.c",
|
||||
"tinystdio/setbuf.c",
|
||||
"tinystdio/setvbuf.c",
|
||||
//"tinystdio/sflags.c", // posix-io
|
||||
"tinystdio/snprintf.c",
|
||||
"tinystdio/snprintfd.c",
|
||||
"tinystdio/snprintff.c",
|
||||
"tinystdio/sprintf.c",
|
||||
"tinystdio/sprintfd.c",
|
||||
"tinystdio/sprintff.c",
|
||||
"tinystdio/sscanf.c",
|
||||
"tinystdio/strfromd.c",
|
||||
"tinystdio/strfromf.c",
|
||||
"tinystdio/strtod.c",
|
||||
"tinystdio/strtod_l.c",
|
||||
"tinystdio/strtof.c",
|
||||
//"tinystdio/strtold.c", // have_long_double and not long_double_equals_double
|
||||
//"tinystdio/strtold_l.c", // have_long_double and not long_double_equals_double
|
||||
"tinystdio/ungetc.c",
|
||||
"tinystdio/vasprintf.c",
|
||||
"tinystdio/vfiprintf.c",
|
||||
"tinystdio/vfiscanf.c",
|
||||
"tinystdio/vfprintf.c",
|
||||
"tinystdio/vfprintff.c",
|
||||
"tinystdio/vfscanf.c",
|
||||
"tinystdio/vfscanff.c",
|
||||
"tinystdio/vprintf.c",
|
||||
"tinystdio/vscanf.c",
|
||||
"tinystdio/vsnprintf.c",
|
||||
"tinystdio/vsprintf.c",
|
||||
"tinystdio/vsscanf.c",
|
||||
|
||||
"string/bcmp.c",
|
||||
"string/bcopy.c",
|
||||
"string/bzero.c",
|
||||
|
||||
+612
-109
@@ -1,16 +1,31 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/dwarf"
|
||||
"debug/elf"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aykevl/go-wasm"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Set to true to print extra debug logs.
|
||||
const sizesDebug = false
|
||||
|
||||
// programSize contains size statistics per package of a compiled program.
|
||||
type programSize struct {
|
||||
Packages map[string]*packageSize
|
||||
Sum *packageSize
|
||||
Packages map[string]packageSize
|
||||
Code uint64
|
||||
ROData uint64
|
||||
Data uint64
|
||||
BSS uint64
|
||||
}
|
||||
@@ -26,6 +41,16 @@ func (ps *programSize) sortedPackageNames() []string {
|
||||
return names
|
||||
}
|
||||
|
||||
// Flash usage in regular microcontrollers.
|
||||
func (ps *programSize) Flash() uint64 {
|
||||
return ps.Code + ps.ROData + ps.Data
|
||||
}
|
||||
|
||||
// Static RAM usage in regular microcontrollers.
|
||||
func (ps *programSize) RAM() uint64 {
|
||||
return ps.Data + ps.BSS
|
||||
}
|
||||
|
||||
// packageSize contains the size of a package, calculated from the linked object
|
||||
// file.
|
||||
type packageSize struct {
|
||||
@@ -45,129 +70,607 @@ func (ps *packageSize) RAM() uint64 {
|
||||
return ps.Data + ps.BSS
|
||||
}
|
||||
|
||||
type symbolList []elf.Symbol
|
||||
|
||||
func (l symbolList) Len() int {
|
||||
return len(l)
|
||||
// A mapping of a single chunk of code or data to a file path.
|
||||
type addressLine struct {
|
||||
Address uint64
|
||||
Length uint64 // length of this chunk
|
||||
File string // file path as stored in DWARF
|
||||
IsVariable bool // true if this is a variable (or constant), false if it is code
|
||||
}
|
||||
|
||||
func (l symbolList) Less(i, j int) bool {
|
||||
bind_i := elf.ST_BIND(l[i].Info)
|
||||
bind_j := elf.ST_BIND(l[j].Info)
|
||||
if l[i].Value == l[j].Value && bind_i != elf.STB_WEAK && bind_j == elf.STB_WEAK {
|
||||
// sort weak symbols after non-weak symbols
|
||||
return true
|
||||
// Sections defined in the input file. This struct defines them in a
|
||||
// filetype-agnostic way but roughly follow the ELF types (.text, .data, .bss,
|
||||
// etc).
|
||||
type memorySection struct {
|
||||
Type memoryType
|
||||
Address uint64
|
||||
Size uint64
|
||||
}
|
||||
|
||||
type memoryType int
|
||||
|
||||
const (
|
||||
memoryCode memoryType = iota + 1
|
||||
memoryData
|
||||
memoryROData
|
||||
memoryBSS
|
||||
memoryStack
|
||||
)
|
||||
|
||||
func (t memoryType) String() string {
|
||||
return [...]string{
|
||||
0: "-",
|
||||
memoryCode: "code",
|
||||
memoryData: "data",
|
||||
memoryROData: "rodata",
|
||||
memoryBSS: "bss",
|
||||
memoryStack: "stack",
|
||||
}[t]
|
||||
}
|
||||
|
||||
// Regular expressions to match particular symbol names. These are not stored as
|
||||
// DWARF variables because they have no mapping to source code global variables.
|
||||
var (
|
||||
// Various globals that aren't a variable but nonetheless need to be stored
|
||||
// somewhere:
|
||||
// alloc: heap allocations during init interpretation
|
||||
// pack: data created when storing a constant in an interface for example
|
||||
// string: buffer behind strings
|
||||
packageSymbolRegexp = regexp.MustCompile(`\$(alloc|pack|string)(\.[0-9]+)?$`)
|
||||
|
||||
// Reflect sidetables. Created by the reflect lowering pass.
|
||||
// See src/reflect/sidetables.go.
|
||||
reflectDataRegexp = regexp.MustCompile(`^reflect\.[a-zA-Z]+Sidetable$`)
|
||||
)
|
||||
|
||||
// readProgramSizeFromDWARF reads the source location for each line of code and
|
||||
// each variable in the program, as far as this is stored in the DWARF debug
|
||||
// information.
|
||||
func readProgramSizeFromDWARF(data *dwarf.Data, codeOffset uint64) ([]addressLine, error) {
|
||||
r := data.Reader()
|
||||
var lines []*dwarf.LineFile
|
||||
var addresses []addressLine
|
||||
for {
|
||||
e, err := r.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if e == nil {
|
||||
break
|
||||
}
|
||||
switch e.Tag {
|
||||
case dwarf.TagCompileUnit:
|
||||
// Found a compile unit.
|
||||
// We can read the .debug_line section using it, which contains a
|
||||
// mapping for most instructions to their file/line/column - even
|
||||
// for inlined functions!
|
||||
lr, err := data.LineReader(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lines = lr.Files()
|
||||
var lineEntry = dwarf.LineEntry{
|
||||
EndSequence: true,
|
||||
}
|
||||
|
||||
// Line tables are organized as sequences of line entries until an
|
||||
// end sequence. A single line table can contain multiple such
|
||||
// sequences. The last line entry is an EndSequence to indicate the
|
||||
// end.
|
||||
for {
|
||||
// Read the next .debug_line entry.
|
||||
prevLineEntry := lineEntry
|
||||
err := lr.Next(&lineEntry)
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if prevLineEntry.EndSequence && lineEntry.Address == 0 {
|
||||
// Tombstone value. This symbol has been removed, for
|
||||
// example by the --gc-sections linker flag. It is still
|
||||
// here in the debug information because the linker can't
|
||||
// just remove this reference.
|
||||
// Read until the next EndSequence so that this sequence is
|
||||
// skipped.
|
||||
// For more details, see (among others):
|
||||
// https://reviews.llvm.org/D84825
|
||||
for {
|
||||
err := lr.Next(&lineEntry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lineEntry.EndSequence {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !prevLineEntry.EndSequence {
|
||||
// The chunk describes the code from prevLineEntry to
|
||||
// lineEntry.
|
||||
line := addressLine{
|
||||
Address: prevLineEntry.Address + codeOffset,
|
||||
Length: lineEntry.Address - prevLineEntry.Address,
|
||||
File: prevLineEntry.File.Name,
|
||||
}
|
||||
if line.Length != 0 {
|
||||
addresses = append(addresses, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
case dwarf.TagVariable:
|
||||
// Global variable (or constant). Most of these are not actually
|
||||
// stored in the binary, because they have been optimized out. Only
|
||||
// the ones with a location are still present.
|
||||
r.SkipChildren()
|
||||
|
||||
file := e.AttrField(dwarf.AttrDeclFile)
|
||||
location := e.AttrField(dwarf.AttrLocation)
|
||||
globalType := e.AttrField(dwarf.AttrType)
|
||||
if file == nil || location == nil || globalType == nil {
|
||||
// Doesn't contain the requested information.
|
||||
continue
|
||||
}
|
||||
|
||||
// Try to parse the location. While this could in theory be a very
|
||||
// complex expression, usually it's just a DW_OP_addr opcode
|
||||
// followed by an address.
|
||||
locationCode := location.Val.([]uint8)
|
||||
if locationCode[0] != 3 { // DW_OP_addr
|
||||
continue
|
||||
}
|
||||
var addr uint64
|
||||
switch len(locationCode) {
|
||||
case 1 + 2:
|
||||
addr = uint64(binary.LittleEndian.Uint16(locationCode[1:]))
|
||||
case 1 + 4:
|
||||
addr = uint64(binary.LittleEndian.Uint32(locationCode[1:]))
|
||||
case 1 + 8:
|
||||
addr = binary.LittleEndian.Uint64(locationCode[1:])
|
||||
default:
|
||||
continue // unknown address
|
||||
}
|
||||
|
||||
// Parse the type of the global variable, which (importantly)
|
||||
// contains the variable size. We're not interested in the type,
|
||||
// only in the size.
|
||||
typ, err := data.Type(globalType.Val.(dwarf.Offset))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addresses = append(addresses, addressLine{
|
||||
Address: addr,
|
||||
Length: uint64(typ.Size()),
|
||||
File: lines[file.Val.(int64)].Name,
|
||||
IsVariable: true,
|
||||
})
|
||||
default:
|
||||
r.SkipChildren()
|
||||
}
|
||||
}
|
||||
return l[i].Value < l[j].Value
|
||||
}
|
||||
|
||||
func (l symbolList) Swap(i, j int) {
|
||||
l[i], l[j] = l[j], l[i]
|
||||
return addresses, nil
|
||||
}
|
||||
|
||||
// loadProgramSize calculate a program/data size breakdown of each package for a
|
||||
// given ELF file.
|
||||
func loadProgramSize(path string) (*programSize, error) {
|
||||
file, err := elf.Open(path)
|
||||
// If the file doesn't contain DWARF debug information, the returned program
|
||||
// size will still have valid summaries but won't have complete size information
|
||||
// per package.
|
||||
func loadProgramSize(path string, packagePathMap map[string]string) (*programSize, error) {
|
||||
// Open the binary file.
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
defer f.Close()
|
||||
|
||||
var sumCode uint64
|
||||
var sumData uint64
|
||||
var sumBSS uint64
|
||||
for _, section := range file.Sections {
|
||||
if section.Flags&elf.SHF_ALLOC == 0 {
|
||||
continue
|
||||
}
|
||||
if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS {
|
||||
continue
|
||||
}
|
||||
if section.Name == ".stack" {
|
||||
// HACK: this works around a bug in ld.lld from LLVM 10. The linker
|
||||
// marks sections with no input symbols (such as is the case for the
|
||||
// .stack section) as SHT_PROGBITS instead of SHT_NOBITS. While it
|
||||
// doesn't affect the generated binaries (.hex and .bin), it does
|
||||
// affect the reported size.
|
||||
// https://bugs.llvm.org/show_bug.cgi?id=45336
|
||||
// https://reviews.llvm.org/D76981
|
||||
// It has been merged in master, but it has not (yet) been
|
||||
// backported to the LLVM 10 release branch.
|
||||
sumBSS += section.Size
|
||||
} else if section.Type == elf.SHT_NOBITS {
|
||||
sumBSS += section.Size
|
||||
} else if section.Flags&elf.SHF_EXECINSTR != 0 {
|
||||
sumCode += section.Size
|
||||
} else if section.Flags&elf.SHF_WRITE != 0 {
|
||||
sumData += section.Size
|
||||
}
|
||||
}
|
||||
// This stores all chunks of addresses found in the binary.
|
||||
var addresses []addressLine
|
||||
|
||||
allSymbols, err := file.Symbols()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
symbols := make([]elf.Symbol, 0, len(allSymbols))
|
||||
for _, symbol := range allSymbols {
|
||||
symType := elf.ST_TYPE(symbol.Info)
|
||||
if symbol.Size == 0 {
|
||||
continue
|
||||
}
|
||||
if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE {
|
||||
continue
|
||||
}
|
||||
if symbol.Section >= elf.SectionIndex(len(file.Sections)) {
|
||||
continue
|
||||
}
|
||||
section := file.Sections[symbol.Section]
|
||||
if section.Flags&elf.SHF_ALLOC == 0 {
|
||||
continue
|
||||
}
|
||||
symbols = append(symbols, symbol)
|
||||
}
|
||||
sort.Sort(symbolList(symbols))
|
||||
|
||||
sizes := map[string]*packageSize{}
|
||||
var lastSymbolValue uint64
|
||||
for _, symbol := range symbols {
|
||||
symType := elf.ST_TYPE(symbol.Info)
|
||||
//bind := elf.ST_BIND(symbol.Info)
|
||||
section := file.Sections[symbol.Section]
|
||||
pkgName := "(bootstrap)"
|
||||
symName := strings.TrimLeft(symbol.Name, "(*")
|
||||
dot := strings.IndexByte(symName, '.')
|
||||
if dot > 0 {
|
||||
pkgName = symName[:dot]
|
||||
}
|
||||
pkgSize := sizes[pkgName]
|
||||
if pkgSize == nil {
|
||||
pkgSize = &packageSize{}
|
||||
sizes[pkgName] = pkgSize
|
||||
}
|
||||
if lastSymbolValue != symbol.Value || lastSymbolValue == 0 {
|
||||
if symType == elf.STT_FUNC {
|
||||
pkgSize.Code += symbol.Size
|
||||
} else if section.Flags&elf.SHF_WRITE != 0 {
|
||||
if section.Type == elf.SHT_NOBITS {
|
||||
pkgSize.BSS += symbol.Size
|
||||
} else {
|
||||
pkgSize.Data += symbol.Size
|
||||
}
|
||||
} else {
|
||||
pkgSize.ROData += symbol.Size
|
||||
// Load the binary file, which could be in a number of file formats.
|
||||
var sections []memorySection
|
||||
if file, err := elf.NewFile(f); err == nil {
|
||||
// Read DWARF information. The error is intentionally ignored.
|
||||
data, _ := file.DWARF()
|
||||
if data != nil {
|
||||
addresses, err = readProgramSizeFromDWARF(data, 0)
|
||||
if err != nil {
|
||||
// However, _do_ report an error here. Something must have gone
|
||||
// wrong while trying to parse DWARF data.
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
lastSymbolValue = symbol.Value
|
||||
|
||||
// Read the ELF symbols for some more chunks of location information.
|
||||
// Some globals (such as strings) aren't stored in the DWARF debug
|
||||
// information and therefore need to be obtained in a different way.
|
||||
allSymbols, err := file.Symbols()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, symbol := range allSymbols {
|
||||
symType := elf.ST_TYPE(symbol.Info)
|
||||
if symbol.Size == 0 {
|
||||
continue
|
||||
}
|
||||
if symType != elf.STT_FUNC && symType != elf.STT_OBJECT && symType != elf.STT_NOTYPE {
|
||||
continue
|
||||
}
|
||||
if symbol.Section >= elf.SHN_LORESERVE {
|
||||
// Not a regular section, so skip it.
|
||||
// One example is elf.SHN_ABS, which is used for symbols
|
||||
// declared with an absolute value such as the memset function
|
||||
// on the ESP32 which is defined in the mask ROM.
|
||||
continue
|
||||
}
|
||||
section := file.Sections[symbol.Section]
|
||||
if section.Flags&elf.SHF_ALLOC == 0 {
|
||||
continue
|
||||
}
|
||||
if packageSymbolRegexp.MatchString(symbol.Name) || reflectDataRegexp.MatchString(symbol.Name) {
|
||||
addresses = append(addresses, addressLine{
|
||||
Address: symbol.Value,
|
||||
Length: symbol.Size,
|
||||
File: symbol.Name,
|
||||
IsVariable: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Load allocated sections.
|
||||
for _, section := range file.Sections {
|
||||
if section.Flags&elf.SHF_ALLOC == 0 {
|
||||
continue
|
||||
}
|
||||
if section.Type == elf.SHT_NOBITS {
|
||||
if section.Name == ".stack" {
|
||||
// TinyGo emits stack sections on microcontroller using the
|
||||
// ".stack" name.
|
||||
// This is a bit ugly, but I don't think there is a way to
|
||||
// mark the stack section in a linker script.
|
||||
sections = append(sections, memorySection{
|
||||
Address: section.Addr,
|
||||
Size: section.Size,
|
||||
Type: memoryStack,
|
||||
})
|
||||
} else {
|
||||
// Regular .bss section.
|
||||
sections = append(sections, memorySection{
|
||||
Address: section.Addr,
|
||||
Size: section.Size,
|
||||
Type: memoryBSS,
|
||||
})
|
||||
}
|
||||
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_EXECINSTR != 0 {
|
||||
// .text
|
||||
sections = append(sections, memorySection{
|
||||
Address: section.Addr,
|
||||
Size: section.Size,
|
||||
Type: memoryCode,
|
||||
})
|
||||
} else if section.Type == elf.SHT_PROGBITS && section.Flags&elf.SHF_WRITE != 0 {
|
||||
// .data
|
||||
sections = append(sections, memorySection{
|
||||
Address: section.Addr,
|
||||
Size: section.Size,
|
||||
Type: memoryData,
|
||||
})
|
||||
} else if section.Type == elf.SHT_PROGBITS {
|
||||
// .rodata
|
||||
sections = append(sections, memorySection{
|
||||
Address: section.Addr,
|
||||
Size: section.Size,
|
||||
Type: memoryROData,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else if file, err := pe.NewFile(f); err == nil {
|
||||
// Read DWARF information. The error is intentionally ignored.
|
||||
data, _ := file.DWARF()
|
||||
if data != nil {
|
||||
addresses, err = readProgramSizeFromDWARF(data, 0)
|
||||
if err != nil {
|
||||
// However, _do_ report an error here. Something must have gone
|
||||
// wrong while trying to parse DWARF data.
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Read COFF sections.
|
||||
optionalHeader := file.OptionalHeader.(*pe.OptionalHeader64)
|
||||
for _, section := range file.Sections {
|
||||
// For more information:
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header
|
||||
const (
|
||||
IMAGE_SCN_CNT_CODE = 0x00000020
|
||||
IMAGE_SCN_CNT_INITIALIZED_DATA = 0x00000040
|
||||
IMAGE_SCN_MEM_DISCARDABLE = 0x02000000
|
||||
IMAGE_SCN_MEM_READ = 0x40000000
|
||||
IMAGE_SCN_MEM_WRITE = 0x80000000
|
||||
)
|
||||
if section.Characteristics&IMAGE_SCN_MEM_DISCARDABLE != 0 {
|
||||
// Debug sections, etc.
|
||||
continue
|
||||
}
|
||||
address := uint64(section.VirtualAddress) + optionalHeader.ImageBase
|
||||
if section.Characteristics&IMAGE_SCN_CNT_CODE != 0 {
|
||||
// .text
|
||||
sections = append(sections, memorySection{
|
||||
Address: address,
|
||||
Size: uint64(section.VirtualSize),
|
||||
Type: memoryCode,
|
||||
})
|
||||
} else if section.Characteristics&IMAGE_SCN_CNT_INITIALIZED_DATA != 0 {
|
||||
if section.Characteristics&IMAGE_SCN_MEM_WRITE != 0 {
|
||||
// .data
|
||||
sections = append(sections, memorySection{
|
||||
Address: address,
|
||||
Size: uint64(section.Size),
|
||||
Type: memoryData,
|
||||
})
|
||||
if section.Size < section.VirtualSize {
|
||||
// Equivalent of a .bss section.
|
||||
// Note: because of how the PE/COFF format is
|
||||
// structured, not all zero-initialized data is marked
|
||||
// as such. A portion may be at the end of the .data
|
||||
// section and is thus marked as initialized data.
|
||||
sections = append(sections, memorySection{
|
||||
Address: address + uint64(section.Size),
|
||||
Size: uint64(section.VirtualSize) - uint64(section.Size),
|
||||
Type: memoryBSS,
|
||||
})
|
||||
}
|
||||
} else if section.Characteristics&IMAGE_SCN_MEM_READ != 0 {
|
||||
// .rdata, .buildid, .pdata
|
||||
sections = append(sections, memorySection{
|
||||
Address: address,
|
||||
Size: uint64(section.VirtualSize),
|
||||
Type: memoryROData,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if file, err := wasm.Parse(f); err == nil {
|
||||
// File is in WebAssembly format.
|
||||
|
||||
// Put code at a very high address, so that it won't conflict with the
|
||||
// data in the memory section.
|
||||
const codeOffset = 0x8000_0000_0000_0000
|
||||
|
||||
// Read DWARF information. The error is intentionally ignored.
|
||||
data, err := file.DWARF()
|
||||
if data != nil {
|
||||
addresses, err = readProgramSizeFromDWARF(data, codeOffset)
|
||||
if err != nil {
|
||||
// However, _do_ report an error here. Something must have gone
|
||||
// wrong while trying to parse DWARF data.
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var linearMemorySize uint64
|
||||
for _, section := range file.Sections {
|
||||
switch section := section.(type) {
|
||||
case *wasm.SectionCode:
|
||||
sections = append(sections, memorySection{
|
||||
Address: codeOffset,
|
||||
Size: uint64(section.Size()),
|
||||
Type: memoryCode,
|
||||
})
|
||||
case *wasm.SectionMemory:
|
||||
// This value is used when processing *wasm.SectionData (which
|
||||
// always comes after *wasm.SectionMemory).
|
||||
linearMemorySize = uint64(section.Entries[0].Limits.Initial) * 64 * 1024
|
||||
case *wasm.SectionData:
|
||||
// Data sections contain initial values for linear memory.
|
||||
// First load the list of data sections, and sort them by
|
||||
// address for easier processing.
|
||||
var dataSections []memorySection
|
||||
for _, entry := range section.Entries {
|
||||
address, err := wasm.Eval(bytes.NewBuffer(entry.Offset))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse data section address: %w", err)
|
||||
}
|
||||
dataSections = append(dataSections, memorySection{
|
||||
Address: uint64(address[0].(int32)),
|
||||
Size: uint64(len(entry.Data)),
|
||||
Type: memoryData,
|
||||
})
|
||||
}
|
||||
sort.Slice(dataSections, func(i, j int) bool {
|
||||
return dataSections[i].Address < dataSections[j].Address
|
||||
})
|
||||
|
||||
// And now add all data sections for linear memory.
|
||||
// Parts that are in the slice of data sections are added as
|
||||
// memoryData, and parts that are not are added as memoryBSS.
|
||||
addr := uint64(0)
|
||||
for _, section := range dataSections {
|
||||
if addr < section.Address {
|
||||
sections = append(sections, memorySection{
|
||||
Address: addr,
|
||||
Size: section.Address - addr,
|
||||
Type: memoryBSS,
|
||||
})
|
||||
}
|
||||
if addr > section.Address {
|
||||
// This might be allowed, I'm not sure.
|
||||
// It certainly doesn't make a lot of sense.
|
||||
return nil, fmt.Errorf("overlapping data section")
|
||||
}
|
||||
// addr == section.Address
|
||||
sections = append(sections, section)
|
||||
addr = section.Address + section.Size
|
||||
}
|
||||
if addr < linearMemorySize {
|
||||
sections = append(sections, memorySection{
|
||||
Address: addr,
|
||||
Size: linearMemorySize - addr,
|
||||
Type: memoryBSS,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("could not parse file: %w", err)
|
||||
}
|
||||
|
||||
sum := &packageSize{}
|
||||
// Sort the slice of address chunks by address, so that we can iterate
|
||||
// through it to calculate section sizes.
|
||||
sort.Slice(addresses, func(i, j int) bool {
|
||||
if addresses[i].Address == addresses[j].Address {
|
||||
// Very rarely, there might be duplicate addresses.
|
||||
// If that happens, sort the largest chunks first.
|
||||
return addresses[i].Length > addresses[j].Length
|
||||
}
|
||||
return addresses[i].Address < addresses[j].Address
|
||||
})
|
||||
|
||||
// Now finally determine the binary/RAM size usage per package by going
|
||||
// through each allocated section.
|
||||
sizes := make(map[string]packageSize)
|
||||
for _, section := range sections {
|
||||
switch section.Type {
|
||||
case memoryCode:
|
||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
||||
field := sizes[path]
|
||||
if isVariable {
|
||||
field.ROData += size
|
||||
} else {
|
||||
field.Code += size
|
||||
}
|
||||
sizes[path] = field
|
||||
}, packagePathMap)
|
||||
case memoryROData:
|
||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
||||
field := sizes[path]
|
||||
field.ROData += size
|
||||
sizes[path] = field
|
||||
}, packagePathMap)
|
||||
case memoryData:
|
||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
||||
field := sizes[path]
|
||||
field.Data += size
|
||||
sizes[path] = field
|
||||
}, packagePathMap)
|
||||
case memoryBSS:
|
||||
readSection(section, addresses, func(path string, size uint64, isVariable bool) {
|
||||
field := sizes[path]
|
||||
field.BSS += size
|
||||
sizes[path] = field
|
||||
}, packagePathMap)
|
||||
case memoryStack:
|
||||
// We store the C stack as a pseudo-package.
|
||||
sizes["C stack"] = packageSize{
|
||||
BSS: section.Size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...and summarize the results.
|
||||
program := &programSize{
|
||||
Packages: sizes,
|
||||
}
|
||||
for _, pkg := range sizes {
|
||||
sum.Code += pkg.Code
|
||||
sum.ROData += pkg.ROData
|
||||
sum.Data += pkg.Data
|
||||
sum.BSS += pkg.BSS
|
||||
program.Code += pkg.Code
|
||||
program.ROData += pkg.ROData
|
||||
program.Data += pkg.Data
|
||||
program.BSS += pkg.BSS
|
||||
}
|
||||
|
||||
return &programSize{Packages: sizes, Code: sumCode, Data: sumData, BSS: sumBSS, Sum: sum}, nil
|
||||
return program, nil
|
||||
}
|
||||
|
||||
// readSection determines for each byte in this section to which package it
|
||||
// belongs. It reports this usage through the addSize callback.
|
||||
func readSection(section memorySection, addresses []addressLine, addSize func(string, uint64, bool), packagePathMap map[string]string) {
|
||||
// The addr variable tracks at which address we are while going through this
|
||||
// section. We start at the beginning.
|
||||
addr := section.Address
|
||||
sectionEnd := section.Address + section.Size
|
||||
if sizesDebug {
|
||||
fmt.Printf("%08x..%08x %5d: %s\n", addr, sectionEnd, section.Size, section.Type)
|
||||
}
|
||||
for _, line := range addresses {
|
||||
if line.Address < section.Address || line.Address+line.Length > sectionEnd {
|
||||
// Check that this line is entirely within the section.
|
||||
// Don't bother dealing with line entries that cross sections (that
|
||||
// seems rather unlikely anyway).
|
||||
continue
|
||||
}
|
||||
if addr < line.Address {
|
||||
// There is a gap: there is a space between the current and the
|
||||
// previous line entry.
|
||||
addSize("(unknown)", line.Address-addr, false)
|
||||
if sizesDebug {
|
||||
fmt.Printf("%08x..%08x %5d: unknown (gap)\n", addr, line.Address, line.Address-addr)
|
||||
}
|
||||
}
|
||||
if addr > line.Address+line.Length {
|
||||
// The current line is already covered by a previous line entry.
|
||||
// Simply skip it.
|
||||
continue
|
||||
}
|
||||
// At this point, addr falls within the current line (probably at the
|
||||
// start).
|
||||
length := line.Length
|
||||
if addr > line.Address {
|
||||
// There is some overlap: the previous line entry already covered
|
||||
// part of this line entry. So reduce the length to add to the
|
||||
// remaining bit of the line entry.
|
||||
length = line.Length - (addr - line.Address)
|
||||
}
|
||||
// Finally, mark this chunk of memory as used by the given package.
|
||||
addSize(findPackagePath(line.File, packagePathMap), length, line.IsVariable)
|
||||
addr = line.Address + line.Length
|
||||
}
|
||||
if addr < sectionEnd {
|
||||
// There is a gap at the end of the section.
|
||||
addSize("(unknown)", sectionEnd-addr, false)
|
||||
if sizesDebug {
|
||||
fmt.Printf("%08x..%08x %5d: unknown (end)\n", addr, sectionEnd, sectionEnd-addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findPackagePath returns the Go package (or a pseudo package) for the given
|
||||
// path. It uses some heuristics, for example for some C libraries.
|
||||
func findPackagePath(path string, packagePathMap map[string]string) string {
|
||||
// Check whether this path is part of one of the compiled packages.
|
||||
packagePath, ok := packagePathMap[filepath.Dir(path)]
|
||||
if !ok {
|
||||
if strings.HasPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")) {
|
||||
// Emit C libraries (in the lib subdirectory of TinyGo) as a single
|
||||
// package, with a "C" prefix. For example: "C compiler-rt" for the
|
||||
// compiler runtime library from LLVM.
|
||||
packagePath = "C " + strings.Split(strings.TrimPrefix(path, filepath.Join(goenv.Get("TINYGOROOT"), "lib")), string(os.PathSeparator))[1]
|
||||
} else if packageSymbolRegexp.MatchString(path) {
|
||||
// Parse symbol names like main$alloc or runtime$string.
|
||||
packagePath = path[:strings.LastIndex(path, "$")]
|
||||
} else if reflectDataRegexp.MatchString(path) {
|
||||
// Parse symbol names like reflect.structTypesSidetable.
|
||||
packagePath = "Go reflect data"
|
||||
} else if path == "<Go interface assert>" {
|
||||
// Interface type assert, generated by the interface lowering pass.
|
||||
packagePath = "Go interface assert"
|
||||
} else if path == "<Go interface method>" {
|
||||
// Interface method wrapper (switch over all concrete types),
|
||||
// generated by the interface lowering pass.
|
||||
packagePath = "Go interface method"
|
||||
} else if path == "<stdin>" {
|
||||
// This can happen when the source code (in Go) doesn't have a
|
||||
// source file and uses "-" as the location. Somewhere this is
|
||||
// converted to "<stdin>".
|
||||
// Convert this back to the "-" string. Eventually, this should be
|
||||
// fixed in the compiler.
|
||||
packagePath = "-"
|
||||
} else {
|
||||
// This is some other path. Not sure what it is, so just emit its directory.
|
||||
packagePath = filepath.Dir(path) // fallback
|
||||
}
|
||||
}
|
||||
return packagePath
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
#include <stdlib.h>
|
||||
bool tinygo_clang_driver(int argc, char **argv);
|
||||
bool tinygo_link_elf(int argc, char **argv);
|
||||
bool tinygo_link_mingw(int argc, char **argv);
|
||||
bool tinygo_link_wasm(int argc, char **argv);
|
||||
*/
|
||||
import "C"
|
||||
@@ -24,6 +25,10 @@ const hasBuiltinTools = true
|
||||
// This version actually runs the tools because TinyGo was compiled while
|
||||
// linking statically with LLVM (with the byollvm build tag).
|
||||
func RunTool(tool string, args ...string) error {
|
||||
linker := "elf"
|
||||
if tool == "ld.lld" && len(args) >= 2 && args[0] == "-m" && args[1] == "i386pep" {
|
||||
linker = "mingw"
|
||||
}
|
||||
args = append([]string{"tinygo:" + tool}, args...)
|
||||
|
||||
var cflag *C.char
|
||||
@@ -41,7 +46,14 @@ func RunTool(tool string, args ...string) error {
|
||||
case "clang":
|
||||
ok = C.tinygo_clang_driver(C.int(len(args)), (**C.char)(buf))
|
||||
case "ld.lld":
|
||||
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
|
||||
switch linker {
|
||||
case "elf":
|
||||
ok = C.tinygo_link_elf(C.int(len(args)), (**C.char)(buf))
|
||||
case "mingw":
|
||||
ok = C.tinygo_link_mingw(C.int(len(args)), (**C.char)(buf))
|
||||
default:
|
||||
return errors.New("unknown linker: " + linker)
|
||||
}
|
||||
case "wasm-ld":
|
||||
ok = C.tinygo_link_wasm(C.int(len(args)), (**C.char)(buf))
|
||||
default:
|
||||
|
||||
+6
-15
@@ -9,8 +9,8 @@ import (
|
||||
)
|
||||
|
||||
// runCCompiler invokes a C compiler with the given arguments.
|
||||
func runCCompiler(command string, flags ...string) error {
|
||||
if hasBuiltinTools && command == "clang" {
|
||||
func runCCompiler(flags ...string) error {
|
||||
if hasBuiltinTools {
|
||||
// Compile this with the internal Clang compiler.
|
||||
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
if headerPath == "" {
|
||||
@@ -23,17 +23,8 @@ func runCCompiler(command string, flags ...string) error {
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// Running some other compiler. Maybe it has been defined in the
|
||||
// commands map (unlikely).
|
||||
if cmdNames, ok := commands[command]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
|
||||
// Alternatively, run the compiler directly.
|
||||
cmd := exec.Command(command, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
// Compile this with an external invocation of the Clang compiler.
|
||||
return execCommand("clang", flags...)
|
||||
}
|
||||
|
||||
// link invokes a linker with the given name and flags.
|
||||
@@ -47,8 +38,8 @@ func link(linker string, flags ...string) error {
|
||||
}
|
||||
|
||||
// Fall back to external command.
|
||||
if cmdNames, ok := commands[linker]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
if _, ok := commands[linker]; ok {
|
||||
return execCommand(linker, flags...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(linker, flags...)
|
||||
|
||||
+3
-1
@@ -141,11 +141,13 @@ func split(input []byte, limit int) [][]byte {
|
||||
var block []byte
|
||||
output := make([][]byte, 0, len(input)/limit+1)
|
||||
for len(input) >= limit {
|
||||
// add all blocks
|
||||
block, input = input[:limit], input[limit:]
|
||||
output = append(output, block)
|
||||
}
|
||||
if len(input) > 0 {
|
||||
output = append(output, input[:len(input)])
|
||||
// add remaining block (that isn't full sized)
|
||||
output = append(output, input)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
+248
-209
@@ -14,6 +14,7 @@ package cgo
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
@@ -30,7 +31,8 @@ type cgoPackage struct {
|
||||
generated *ast.File
|
||||
generatedPos token.Pos
|
||||
errors []error
|
||||
dir string
|
||||
currentDir string // current working directory
|
||||
packageDir string // full path to the package to process
|
||||
fset *token.FileSet
|
||||
tokenFiles map[string]*token.File
|
||||
missingSymbols map[string]struct{}
|
||||
@@ -41,7 +43,9 @@ type cgoPackage struct {
|
||||
elaboratedTypes map[string]*elaboratedTypeInfo
|
||||
enums map[string]enumInfo
|
||||
anonStructNum int
|
||||
ldflags []string
|
||||
cflags []string // CFlags from #cgo lines
|
||||
ldflags []string // LDFlags from #cgo lines
|
||||
visitedFiles map[string][]byte
|
||||
}
|
||||
|
||||
// constantInfo stores some information about a CGo constant found by libclang
|
||||
@@ -122,17 +126,17 @@ var cgoAliases = map[string]string{
|
||||
// builtinAliases are handled specially because they only exist on the Go side
|
||||
// of CGo, not on the CGo side (they're prefixed with "_Cgo_" there).
|
||||
var builtinAliases = map[string]struct{}{
|
||||
"char": struct{}{},
|
||||
"schar": struct{}{},
|
||||
"uchar": struct{}{},
|
||||
"short": struct{}{},
|
||||
"ushort": struct{}{},
|
||||
"int": struct{}{},
|
||||
"uint": struct{}{},
|
||||
"long": struct{}{},
|
||||
"ulong": struct{}{},
|
||||
"longlong": struct{}{},
|
||||
"ulonglong": struct{}{},
|
||||
"char": {},
|
||||
"schar": {},
|
||||
"uchar": {},
|
||||
"short": {},
|
||||
"ushort": {},
|
||||
"int": {},
|
||||
"uint": {},
|
||||
"long": {},
|
||||
"ulong": {},
|
||||
"longlong": {},
|
||||
"ulonglong": {},
|
||||
}
|
||||
|
||||
// cgoTypes lists some C types with ambiguous sizes that must be retrieved
|
||||
@@ -153,14 +157,49 @@ typedef long long _Cgo_longlong;
|
||||
typedef unsigned long long _Cgo_ulonglong;
|
||||
`
|
||||
|
||||
// First part of the generated Go file. Written here as Go because that's much
|
||||
// easier than constructing the entire AST in memory.
|
||||
// The string/bytes functions below implement C.CString etc. To make sure the
|
||||
// runtime doesn't need to know the C int type, lengths are converted to uintptr
|
||||
// first.
|
||||
// These functions will be modified to get a "C." prefix, so the source below
|
||||
// doesn't reflect the final AST.
|
||||
const generatedGoFilePrefix = `
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
//go:linkname C.CString runtime.cgo_CString
|
||||
func CString(string) *C.char
|
||||
|
||||
//go:linkname C.GoString runtime.cgo_GoString
|
||||
func GoString(*C.char) string
|
||||
|
||||
//go:linkname C.__GoStringN runtime.cgo_GoStringN
|
||||
func __GoStringN(*C.char, uintptr) string
|
||||
|
||||
func GoStringN(cstr *C.char, length C.int) string {
|
||||
return C.__GoStringN(cstr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__GoBytes runtime.cgo_GoBytes
|
||||
func __GoBytes(unsafe.Pointer, uintptr) []byte
|
||||
|
||||
func GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
`
|
||||
|
||||
// Process extracts `import "C"` statements from the AST, parses the comment
|
||||
// with libclang, and modifies the AST to use this information. It returns a
|
||||
// newly created *ast.File that should be added to the list of to-be-parsed
|
||||
// files. If there is one or more error, it returns these in the []error slice
|
||||
// but still modifies the AST.
|
||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) {
|
||||
// files, the CGo header snippets that should be compiled (for inline
|
||||
// functions), the CFLAGS and LDFLAGS found in #cgo lines, and a map of file
|
||||
// hashes of the accessed C header files. If there is one or more error, it
|
||||
// returns these in the []error slice but still modifies the AST.
|
||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []string, []string, map[string][]byte, []error) {
|
||||
p := &cgoPackage{
|
||||
dir: dir,
|
||||
currentDir: dir,
|
||||
fset: fset,
|
||||
tokenFiles: map[string]*token.File{},
|
||||
missingSymbols: map[string]struct{}{},
|
||||
@@ -170,13 +209,9 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
typedefs: map[string]*typedefInfo{},
|
||||
elaboratedTypes: map[string]*elaboratedTypeInfo{},
|
||||
enums: map[string]enumInfo{},
|
||||
visitedFiles: map[string][]byte{},
|
||||
}
|
||||
|
||||
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
|
||||
// Note that it is only disabled for memcpy (etc) calls made from Go, which
|
||||
// have better alternatives anyway.
|
||||
cflags = append(cflags, "-D_FORTIFY_SOURCE=0")
|
||||
|
||||
// Add a new location for the following file.
|
||||
generatedTokenPos := p.fset.AddFile(dir+"/!cgo.go", -1, 0)
|
||||
generatedTokenPos.SetLines([]int{0})
|
||||
@@ -185,68 +220,41 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
// Find the absolute path for this package.
|
||||
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
|
||||
if err != nil {
|
||||
return nil, nil, []error{
|
||||
return nil, nil, nil, nil, nil, []error{
|
||||
scanner.Error{
|
||||
Pos: fset.Position(files[0].Pos()),
|
||||
Msg: "cgo: cannot find absolute path: " + err.Error(), // TODO: wrap this error
|
||||
},
|
||||
}
|
||||
}
|
||||
packagePath = filepath.Dir(packagePath)
|
||||
p.packageDir = filepath.Dir(packagePath)
|
||||
|
||||
// Construct a new in-memory AST for CGo declarations of this package.
|
||||
unsafeImport := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
ValuePos: p.generatedPos,
|
||||
Kind: token.STRING,
|
||||
Value: "\"unsafe\"",
|
||||
},
|
||||
EndPos: p.generatedPos,
|
||||
// The first part is written as Go code that is then parsed, but more code
|
||||
// is added later to the AST to declare functions, globals, etc.
|
||||
goCode := "package " + files[0].Name.Name + "\n\n" + generatedGoFilePrefix
|
||||
p.generated, err = parser.ParseFile(fset, dir+"/!cgo.go", goCode, parser.ParseComments)
|
||||
if err != nil {
|
||||
// This is always a bug in the cgo package.
|
||||
panic("unexpected error: " + err.Error())
|
||||
}
|
||||
p.generated = &ast.File{
|
||||
Package: p.generatedPos,
|
||||
Name: &ast.Ident{
|
||||
NamePos: p.generatedPos,
|
||||
Name: files[0].Name.Name,
|
||||
},
|
||||
Decls: []ast.Decl{
|
||||
// import "unsafe"
|
||||
&ast.GenDecl{
|
||||
TokPos: p.generatedPos,
|
||||
Tok: token.IMPORT,
|
||||
Specs: []ast.Spec{
|
||||
unsafeImport,
|
||||
},
|
||||
},
|
||||
// var _ unsafe.Pointer
|
||||
// This avoids type errors when the unsafe package is never used.
|
||||
&ast.GenDecl{
|
||||
Tok: token.VAR,
|
||||
Specs: []ast.Spec{
|
||||
&ast.ValueSpec{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
Name: "_",
|
||||
Obj: &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "_",
|
||||
},
|
||||
},
|
||||
},
|
||||
Type: &ast.SelectorExpr{
|
||||
X: &ast.Ident{
|
||||
Name: "unsafe",
|
||||
},
|
||||
Sel: &ast.Ident{
|
||||
Name: "Pointer",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Imports: []*ast.ImportSpec{unsafeImport},
|
||||
// If the Comments field is not set to nil, the fmt package will get
|
||||
// confused about where comments should go.
|
||||
p.generated.Comments = nil
|
||||
// Adjust some of the functions in there.
|
||||
for _, decl := range p.generated.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.FuncDecl:
|
||||
switch decl.Name.Name {
|
||||
case "CString", "GoString", "GoStringN", "__GoStringN", "GoBytes", "__GoBytes":
|
||||
// Adjust the name to have a "C." prefix so it is correctly
|
||||
// resolved.
|
||||
decl.Name.Name = "C." + decl.Name.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
// Patch some types, for example *C.char in C.CString.
|
||||
astutil.Apply(p.generated, p.walker, nil)
|
||||
|
||||
// Find all C.* symbols.
|
||||
for _, f := range files {
|
||||
@@ -256,9 +264,10 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
p.missingSymbols["_Cgo_"+name] = struct{}{}
|
||||
}
|
||||
|
||||
// Find `import "C"` statements in the file.
|
||||
var statements []*ast.GenDecl
|
||||
for _, f := range files {
|
||||
// Find `import "C"` C fragments in the file.
|
||||
cgoHeaders := make([]string, len(files)) // combined CGo header fragment for each file
|
||||
for i, f := range files {
|
||||
var cgoHeader string
|
||||
for i := 0; i < len(f.Decls); i++ {
|
||||
decl := f.Decls[i]
|
||||
genDecl, ok := decl.(*ast.GenDecl)
|
||||
@@ -282,117 +291,52 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
continue
|
||||
}
|
||||
|
||||
// Found a CGo statement.
|
||||
statements = append(statements, genDecl)
|
||||
|
||||
// Remove this import declaration.
|
||||
f.Decls = append(f.Decls[:i], f.Decls[i+1:]...)
|
||||
i--
|
||||
}
|
||||
|
||||
// Print the AST, for debugging.
|
||||
//ast.Print(fset, f)
|
||||
}
|
||||
|
||||
// Find all #cgo lines.
|
||||
for _, genDecl := range statements {
|
||||
if genDecl.Doc == nil {
|
||||
continue
|
||||
}
|
||||
for _, comment := range genDecl.Doc.List {
|
||||
for {
|
||||
// Extract the #cgo line, and replace it with spaces.
|
||||
// Replacing with spaces makes sure that error locations are
|
||||
// still correct, while not interfering with parsing in any way.
|
||||
lineStart := strings.Index(comment.Text, "#cgo ")
|
||||
if lineStart < 0 {
|
||||
break
|
||||
}
|
||||
lineLen := strings.IndexByte(comment.Text[lineStart:], '\n')
|
||||
if lineLen < 0 {
|
||||
lineLen = len(comment.Text) - lineStart
|
||||
}
|
||||
lineEnd := lineStart + lineLen
|
||||
line := comment.Text[lineStart:lineEnd]
|
||||
spaces := make([]byte, len(line))
|
||||
for i := range spaces {
|
||||
spaces[i] = ' '
|
||||
}
|
||||
lenBefore := len(comment.Text)
|
||||
comment.Text = comment.Text[:lineStart] + string(spaces) + comment.Text[lineEnd:]
|
||||
if len(comment.Text) != lenBefore {
|
||||
println(lenBefore, len(comment.Text))
|
||||
panic("length of preamble changed!")
|
||||
}
|
||||
|
||||
// Get the text before the colon in the #cgo directive.
|
||||
colon := strings.IndexByte(line, ':')
|
||||
if colon < 0 {
|
||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart], "missing colon in #cgo line")
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract the fields before the colon. These fields are a list
|
||||
// of build tags and the C environment variable.
|
||||
fields := strings.Fields(line[4:colon])
|
||||
if len(fields) == 0 {
|
||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon-1], "invalid #cgo line")
|
||||
continue
|
||||
}
|
||||
|
||||
if len(fields) > 1 {
|
||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+5], "not implemented: build constraints in #cgo line")
|
||||
continue
|
||||
}
|
||||
|
||||
name := fields[len(fields)-1]
|
||||
value := line[colon+1:]
|
||||
switch name {
|
||||
case "CFLAGS":
|
||||
flags, err := shlex.Split(value)
|
||||
if err != nil {
|
||||
// TODO: find the exact location where the error happened.
|
||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
|
||||
continue
|
||||
}
|
||||
if err := checkCompilerFlags(name, flags); err != nil {
|
||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
|
||||
continue
|
||||
}
|
||||
makePathsAbsolute(flags, packagePath)
|
||||
cflags = append(cflags, flags...)
|
||||
case "LDFLAGS":
|
||||
flags, err := shlex.Split(value)
|
||||
if err != nil {
|
||||
// TODO: find the exact location where the error happened.
|
||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
|
||||
continue
|
||||
}
|
||||
if err := checkLinkerFlags(name, flags); err != nil {
|
||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+colon+1], err.Error())
|
||||
continue
|
||||
}
|
||||
makePathsAbsolute(flags, packagePath)
|
||||
p.ldflags = append(p.ldflags, flags...)
|
||||
default:
|
||||
startPos := strings.LastIndex(line[4:colon], name) + 4
|
||||
p.addErrorAfter(comment.Slash, comment.Text[:lineStart+startPos], "invalid #cgo line: "+name)
|
||||
continue
|
||||
}
|
||||
if genDecl.Doc == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Iterate through all parts of the CGo header. Note that every //
|
||||
// line is a new comment.
|
||||
position := fset.Position(genDecl.Doc.Pos())
|
||||
fragment := fmt.Sprintf("# %d %#v\n", position.Line, position.Filename)
|
||||
for _, comment := range genDecl.Doc.List {
|
||||
// Find all #cgo lines, extract and use their contents, and
|
||||
// replace the lines with spaces (to preserve locations).
|
||||
c := p.parseCGoPreprocessorLines(comment.Text, comment.Slash)
|
||||
|
||||
// Change the comment (which is still in Go syntax, with // and
|
||||
// /* */ ) to a regular string by replacing the start/end
|
||||
// markers of comments with spaces.
|
||||
// It is similar to the Text() method but differs in that it
|
||||
// doesn't strip anything and tries to keep all offsets correct
|
||||
// by adding spaces and newlines where necessary.
|
||||
if c[1] == '/' { /* comment */
|
||||
c = " " + c[2:]
|
||||
} else { // comment
|
||||
c = " " + c[2:len(c)-2]
|
||||
}
|
||||
fragment += c + "\n"
|
||||
}
|
||||
cgoHeader += fragment
|
||||
}
|
||||
|
||||
cgoHeaders[i] = cgoHeader
|
||||
}
|
||||
|
||||
// Process all CGo imports.
|
||||
for _, genDecl := range statements {
|
||||
cgoComment := genDecl.Doc.Text()
|
||||
// Define CFlags that will be used while parsing the package.
|
||||
// Disable _FORTIFY_SOURCE as it causes problems on macOS.
|
||||
// Note that it is only disabled for memcpy (etc) calls made from Go, which
|
||||
// have better alternatives anyway.
|
||||
cflagsForCGo := append([]string{"-D_FORTIFY_SOURCE=0"}, cflags...)
|
||||
cflagsForCGo = append(cflagsForCGo, p.cflags...)
|
||||
|
||||
pos := genDecl.Pos()
|
||||
if genDecl.Doc != nil {
|
||||
pos = genDecl.Doc.Pos()
|
||||
}
|
||||
position := fset.PositionFor(pos, true)
|
||||
p.parseFragment(cgoComment+cgoTypes, cflags, position.Filename, position.Line)
|
||||
// Process CGo imports for each file.
|
||||
for i, f := range files {
|
||||
p.parseFragment(cgoHeaders[i]+cgoTypes, cflagsForCGo, filepath.Base(fset.File(f.Pos()).Name()))
|
||||
}
|
||||
|
||||
// Declare functions found by libclang.
|
||||
@@ -427,18 +371,18 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
// Print the newly generated in-memory AST, for debugging.
|
||||
//ast.Print(fset, p.generated)
|
||||
|
||||
return p.generated, p.ldflags, p.errors
|
||||
return p.generated, cgoHeaders, p.cflags, p.ldflags, p.visitedFiles, p.errors
|
||||
}
|
||||
|
||||
// makePathsAbsolute converts some common path compiler flags (-I, -L) from
|
||||
// relative flags into absolute flags, if they are relative. This is necessary
|
||||
// because the C compiler is usually not invoked from the package path.
|
||||
func makePathsAbsolute(args []string, packagePath string) {
|
||||
func (p *cgoPackage) makePathsAbsolute(args []string) {
|
||||
nextIsPath := false
|
||||
for i, arg := range args {
|
||||
if nextIsPath {
|
||||
if !filepath.IsAbs(arg) {
|
||||
args[i] = filepath.Join(packagePath, arg)
|
||||
args[i] = filepath.Join(p.packageDir, arg)
|
||||
}
|
||||
}
|
||||
if arg == "-I" || arg == "-L" {
|
||||
@@ -448,12 +392,95 @@ func makePathsAbsolute(args []string, packagePath string) {
|
||||
if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
|
||||
path := arg[2:]
|
||||
if !filepath.IsAbs(path) {
|
||||
args[i] = arg[:2] + filepath.Join(packagePath, path)
|
||||
args[i] = arg[:2] + filepath.Join(p.packageDir, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parseCGoPreprocessorLines reads #cgo pseudo-preprocessor lines in the source
|
||||
// text (import "C" fragment), stores their information such as CFLAGS, and
|
||||
// returns the same text but with those #cgo lines replaced by spaces (to keep
|
||||
// position offsets the same).
|
||||
func (p *cgoPackage) parseCGoPreprocessorLines(text string, pos token.Pos) string {
|
||||
for {
|
||||
// Extract the #cgo line, and replace it with spaces.
|
||||
// Replacing with spaces makes sure that error locations are
|
||||
// still correct, while not interfering with parsing in any way.
|
||||
lineStart := strings.Index(text, "#cgo ")
|
||||
if lineStart < 0 {
|
||||
break
|
||||
}
|
||||
lineLen := strings.IndexByte(text[lineStart:], '\n')
|
||||
if lineLen < 0 {
|
||||
lineLen = len(text) - lineStart
|
||||
}
|
||||
lineEnd := lineStart + lineLen
|
||||
line := text[lineStart:lineEnd]
|
||||
spaces := make([]byte, len(line))
|
||||
for i := range spaces {
|
||||
spaces[i] = ' '
|
||||
}
|
||||
text = text[:lineStart] + string(spaces) + text[lineEnd:]
|
||||
|
||||
// Get the text before the colon in the #cgo directive.
|
||||
colon := strings.IndexByte(line, ':')
|
||||
if colon < 0 {
|
||||
p.addErrorAfter(pos, text[:lineStart], "missing colon in #cgo line")
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract the fields before the colon. These fields are a list
|
||||
// of build tags and the C environment variable.
|
||||
fields := strings.Fields(line[4:colon])
|
||||
if len(fields) == 0 {
|
||||
p.addErrorAfter(pos, text[:lineStart+colon-1], "invalid #cgo line")
|
||||
continue
|
||||
}
|
||||
|
||||
if len(fields) > 1 {
|
||||
p.addErrorAfter(pos, text[:lineStart+5], "not implemented: build constraints in #cgo line")
|
||||
continue
|
||||
}
|
||||
|
||||
name := fields[len(fields)-1]
|
||||
value := line[colon+1:]
|
||||
switch name {
|
||||
case "CFLAGS":
|
||||
flags, err := shlex.Split(value)
|
||||
if err != nil {
|
||||
// TODO: find the exact location where the error happened.
|
||||
p.addErrorAfter(pos, text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
|
||||
continue
|
||||
}
|
||||
if err := checkCompilerFlags(name, flags); err != nil {
|
||||
p.addErrorAfter(pos, text[:lineStart+colon+1], err.Error())
|
||||
continue
|
||||
}
|
||||
p.makePathsAbsolute(flags)
|
||||
p.cflags = append(p.cflags, flags...)
|
||||
case "LDFLAGS":
|
||||
flags, err := shlex.Split(value)
|
||||
if err != nil {
|
||||
// TODO: find the exact location where the error happened.
|
||||
p.addErrorAfter(pos, text[:lineStart+colon+1], "failed to parse flags in #cgo line: "+err.Error())
|
||||
continue
|
||||
}
|
||||
if err := checkLinkerFlags(name, flags); err != nil {
|
||||
p.addErrorAfter(pos, text[:lineStart+colon+1], err.Error())
|
||||
continue
|
||||
}
|
||||
p.makePathsAbsolute(flags)
|
||||
p.ldflags = append(p.ldflags, flags...)
|
||||
default:
|
||||
startPos := strings.LastIndex(line[4:colon], name) + 4
|
||||
p.addErrorAfter(pos, text[:lineStart+startPos], "invalid #cgo line: "+name)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
// addFuncDecls adds the C function declarations found by libclang in the
|
||||
// comment above the `import "C"` statement.
|
||||
func (p *cgoPackage) addFuncDecls() {
|
||||
@@ -470,6 +497,14 @@ func (p *cgoPackage) addFuncDecls() {
|
||||
}
|
||||
args := make([]*ast.Field, len(fn.args))
|
||||
decl := &ast.FuncDecl{
|
||||
Doc: &ast.CommentGroup{
|
||||
List: []*ast.Comment{
|
||||
{
|
||||
Slash: fn.pos - 1,
|
||||
Text: "//export " + name,
|
||||
},
|
||||
},
|
||||
},
|
||||
Name: &ast.Ident{
|
||||
NamePos: fn.pos,
|
||||
Name: "C." + name,
|
||||
@@ -486,20 +521,16 @@ func (p *cgoPackage) addFuncDecls() {
|
||||
},
|
||||
}
|
||||
if fn.variadic {
|
||||
decl.Doc = &ast.CommentGroup{
|
||||
List: []*ast.Comment{
|
||||
&ast.Comment{
|
||||
Slash: fn.pos,
|
||||
Text: "//go:variadic",
|
||||
},
|
||||
},
|
||||
}
|
||||
decl.Doc.List = append(decl.Doc.List, &ast.Comment{
|
||||
Slash: fn.pos - 1,
|
||||
Text: "//go:variadic",
|
||||
})
|
||||
}
|
||||
obj.Decl = decl
|
||||
for i, arg := range fn.args {
|
||||
args[i] = &ast.Field{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
{
|
||||
NamePos: fn.pos,
|
||||
Name: arg.name,
|
||||
Obj: &ast.Object{
|
||||
@@ -547,7 +578,7 @@ func (p *cgoPackage) addFuncPtrDecls() {
|
||||
Name: "C." + name + "$funcaddr",
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
Names: []*ast.Ident{{
|
||||
NamePos: fn.pos,
|
||||
Name: "C." + name + "$funcaddr",
|
||||
Obj: obj,
|
||||
@@ -597,7 +628,7 @@ func (p *cgoPackage) addConstDecls() {
|
||||
Name: "C." + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
Names: []*ast.Ident{{
|
||||
NamePos: constVal.pos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
@@ -626,19 +657,27 @@ func (p *cgoPackage) addVarDecls() {
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, name := range names {
|
||||
global := p.globals[name]
|
||||
gen := &ast.GenDecl{
|
||||
TokPos: token.NoPos,
|
||||
TokPos: global.pos,
|
||||
Tok: token.VAR,
|
||||
Lparen: token.NoPos,
|
||||
Rparen: token.NoPos,
|
||||
Doc: &ast.CommentGroup{
|
||||
List: []*ast.Comment{
|
||||
{
|
||||
Slash: global.pos - 1,
|
||||
Text: "//go:extern " + name,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
global := p.globals[name]
|
||||
obj := &ast.Object{
|
||||
Kind: ast.Var,
|
||||
Name: "C." + name,
|
||||
}
|
||||
valueSpec := &ast.ValueSpec{
|
||||
Names: []*ast.Ident{&ast.Ident{
|
||||
Names: []*ast.Ident{{
|
||||
NamePos: global.pos,
|
||||
Name: "C." + name,
|
||||
Obj: obj,
|
||||
@@ -839,9 +878,9 @@ func (p *cgoPackage) makeUnionField(typ *elaboratedTypeInfo) *ast.StructType {
|
||||
Struct: typ.typeExpr.Struct,
|
||||
Fields: &ast.FieldList{
|
||||
Opening: typ.typeExpr.Fields.Opening,
|
||||
List: []*ast.Field{&ast.Field{
|
||||
List: []*ast.Field{{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
{
|
||||
NamePos: typ.typeExpr.Fields.Opening,
|
||||
Name: "$union",
|
||||
},
|
||||
@@ -922,9 +961,9 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
|
||||
Recv: &ast.FieldList{
|
||||
Opening: pos,
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
{
|
||||
NamePos: pos,
|
||||
Name: "union",
|
||||
},
|
||||
@@ -953,7 +992,7 @@ func (p *cgoPackage) createUnionAccessor(field *ast.Field, typeName string) {
|
||||
},
|
||||
Results: &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
{
|
||||
Type: &ast.StarExpr{
|
||||
Star: pos,
|
||||
X: field.Type,
|
||||
@@ -1032,9 +1071,9 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
|
||||
Recv: &ast.FieldList{
|
||||
Opening: bitfield.pos,
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: &ast.Object{
|
||||
@@ -1068,7 +1107,7 @@ func (p *cgoPackage) createBitfieldGetter(bitfield bitfieldInfo, typeName string
|
||||
},
|
||||
Results: &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
{
|
||||
Type: bitfield.field.Type,
|
||||
},
|
||||
},
|
||||
@@ -1185,9 +1224,9 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
Recv: &ast.FieldList{
|
||||
Opening: bitfield.pos,
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "s",
|
||||
Obj: &ast.Object{
|
||||
@@ -1218,9 +1257,9 @@ func (p *cgoPackage) createBitfieldSetter(bitfield bitfieldInfo, typeName string
|
||||
Params: &ast.FieldList{
|
||||
Opening: bitfield.pos,
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
{
|
||||
Names: []*ast.Ident{
|
||||
&ast.Ident{
|
||||
{
|
||||
NamePos: bitfield.pos,
|
||||
Name: "value",
|
||||
Obj: nil,
|
||||
|
||||
+11
-13
@@ -12,7 +12,6 @@ import (
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -25,21 +24,20 @@ var flagUpdate = flag.Bool("update", false, "Update images based on test output.
|
||||
// platforms and Go versions.
|
||||
func normalizeResult(result string) string {
|
||||
actual := strings.ReplaceAll(result, "\r\n", "\n")
|
||||
|
||||
// Make sure all functions are wrapped, even those that would otherwise be
|
||||
// single-line functions. This is necessary because Go 1.14 changed the way
|
||||
// such functions are wrapped and it's important to have consistent test
|
||||
// results.
|
||||
re := regexp.MustCompile(`func \((.+)\)( .*?) +{ (.+) }`)
|
||||
actual = re.ReplaceAllString(actual, "func ($1)$2 {\n\t$3\n}")
|
||||
|
||||
return actual
|
||||
}
|
||||
|
||||
func TestCGo(t *testing.T) {
|
||||
var cflags = []string{"--target=armv6m-none-eabi"}
|
||||
var cflags = []string{"--target=armv6m-unknown-unknown-eabi"}
|
||||
|
||||
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
|
||||
for _, name := range []string{
|
||||
"basic",
|
||||
"errors",
|
||||
"types",
|
||||
"symbols",
|
||||
"flags",
|
||||
"const",
|
||||
} {
|
||||
name := name // avoid a race condition
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Skip tests that require specific Go version.
|
||||
@@ -65,7 +63,7 @@ func TestCGo(t *testing.T) {
|
||||
}
|
||||
|
||||
// Process the AST with CGo.
|
||||
cgoAST, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
|
||||
cgoAST, _, _, _, _, cgoErrors := Process([]*ast.File{f}, "testdata", fset, cflags)
|
||||
|
||||
// Check the AST for type errors.
|
||||
var typecheckErrors []error
|
||||
@@ -105,7 +103,7 @@ func TestCGo(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("could not write out CGo AST: %v", err)
|
||||
}
|
||||
actual := normalizeResult(string(buf.Bytes()))
|
||||
actual := normalizeResult(buf.String())
|
||||
|
||||
// Read the file with the expected output, to compare against.
|
||||
outfile := filepath.Join("testdata", name+".out.go")
|
||||
|
||||
+164
-74
@@ -11,105 +11,184 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
prefixParseFns map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error)
|
||||
precedences = map[token.Token]int{
|
||||
token.ADD: precedenceAdd,
|
||||
token.SUB: precedenceAdd,
|
||||
token.MUL: precedenceMul,
|
||||
token.QUO: precedenceMul,
|
||||
token.REM: precedenceMul,
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
precedenceLowest = iota + 1
|
||||
precedenceAdd
|
||||
precedenceMul
|
||||
precedencePrefix
|
||||
)
|
||||
|
||||
func init() {
|
||||
// This must be done in an init function to avoid an initialization order
|
||||
// failure.
|
||||
prefixParseFns = map[token.Token]func(*tokenizer) (ast.Expr, *scanner.Error){
|
||||
token.IDENT: parseIdent,
|
||||
token.INT: parseBasicLit,
|
||||
token.FLOAT: parseBasicLit,
|
||||
token.STRING: parseBasicLit,
|
||||
token.CHAR: parseBasicLit,
|
||||
token.LPAREN: parseParenExpr,
|
||||
token.SUB: parseUnaryExpr,
|
||||
}
|
||||
}
|
||||
|
||||
// parseConst parses the given string as a C constant.
|
||||
func parseConst(pos token.Pos, fset *token.FileSet, value string) (ast.Expr, *scanner.Error) {
|
||||
t := newTokenizer(pos, fset, value)
|
||||
expr, err := parseConstExpr(t)
|
||||
if t.token != token.EOF {
|
||||
expr, err := parseConstExpr(t, precedenceLowest)
|
||||
t.Next()
|
||||
if t.curToken != token.EOF {
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Msg: "unexpected token " + t.token.String(),
|
||||
Pos: t.fset.Position(t.curPos),
|
||||
Msg: "unexpected token " + t.curToken.String() + ", expected end of expression",
|
||||
}
|
||||
}
|
||||
return expr, err
|
||||
}
|
||||
|
||||
// parseConstExpr parses a stream of C tokens to a Go expression.
|
||||
func parseConstExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
switch t.token {
|
||||
case token.LPAREN:
|
||||
lparen := t.pos
|
||||
t.Next()
|
||||
x, err := parseConstExpr(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if t.token != token.RPAREN {
|
||||
return nil, unexpectedToken(t, token.RPAREN)
|
||||
}
|
||||
expr := &ast.ParenExpr{
|
||||
Lparen: lparen,
|
||||
X: x,
|
||||
Rparen: t.pos,
|
||||
}
|
||||
t.Next()
|
||||
return expr, nil
|
||||
case token.INT, token.FLOAT, token.STRING, token.CHAR:
|
||||
expr := &ast.BasicLit{
|
||||
ValuePos: t.pos,
|
||||
Kind: t.token,
|
||||
Value: t.value,
|
||||
}
|
||||
t.Next()
|
||||
return expr, nil
|
||||
case token.IDENT:
|
||||
expr := &ast.Ident{
|
||||
NamePos: t.pos,
|
||||
Name: "C." + t.value,
|
||||
}
|
||||
t.Next()
|
||||
return expr, nil
|
||||
case token.EOF:
|
||||
func parseConstExpr(t *tokenizer, precedence int) (ast.Expr, *scanner.Error) {
|
||||
if t.curToken == token.EOF {
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Pos: t.fset.Position(t.curPos),
|
||||
Msg: "empty constant",
|
||||
}
|
||||
default:
|
||||
}
|
||||
prefix := prefixParseFns[t.curToken]
|
||||
if prefix == nil {
|
||||
return nil, &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Msg: fmt.Sprintf("unexpected token %s", t.token),
|
||||
Pos: t.fset.Position(t.curPos),
|
||||
Msg: fmt.Sprintf("unexpected token %s", t.curToken),
|
||||
}
|
||||
}
|
||||
leftExpr, err := prefix(t)
|
||||
|
||||
for t.peekToken != token.EOF && precedence < precedences[t.peekToken] {
|
||||
switch t.peekToken {
|
||||
case token.ADD, token.SUB, token.MUL, token.QUO, token.REM:
|
||||
t.Next()
|
||||
leftExpr, err = parseBinaryExpr(t, leftExpr)
|
||||
}
|
||||
}
|
||||
|
||||
return leftExpr, err
|
||||
}
|
||||
|
||||
func parseIdent(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
return &ast.Ident{
|
||||
NamePos: t.curPos,
|
||||
Name: "C." + t.curValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseBasicLit(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
return &ast.BasicLit{
|
||||
ValuePos: t.curPos,
|
||||
Kind: t.curToken,
|
||||
Value: t.curValue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseParenExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
lparen := t.curPos
|
||||
t.Next()
|
||||
x, err := parseConstExpr(t, precedenceLowest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Next()
|
||||
if t.curToken != token.RPAREN {
|
||||
return nil, unexpectedToken(t, token.RPAREN)
|
||||
}
|
||||
expr := &ast.ParenExpr{
|
||||
Lparen: lparen,
|
||||
X: x,
|
||||
Rparen: t.curPos,
|
||||
}
|
||||
return expr, nil
|
||||
}
|
||||
|
||||
func parseBinaryExpr(t *tokenizer, left ast.Expr) (ast.Expr, *scanner.Error) {
|
||||
expression := &ast.BinaryExpr{
|
||||
X: left,
|
||||
Op: t.curToken,
|
||||
OpPos: t.curPos,
|
||||
}
|
||||
precedence := precedences[t.curToken]
|
||||
t.Next()
|
||||
right, err := parseConstExpr(t, precedence)
|
||||
expression.Y = right
|
||||
return expression, err
|
||||
}
|
||||
|
||||
func parseUnaryExpr(t *tokenizer) (ast.Expr, *scanner.Error) {
|
||||
expression := &ast.UnaryExpr{
|
||||
OpPos: t.curPos,
|
||||
Op: t.curToken,
|
||||
}
|
||||
t.Next()
|
||||
x, err := parseConstExpr(t, precedencePrefix)
|
||||
expression.X = x
|
||||
return expression, err
|
||||
}
|
||||
|
||||
// unexpectedToken returns an error of the form "unexpected token FOO, expected
|
||||
// BAR".
|
||||
func unexpectedToken(t *tokenizer, expected token.Token) *scanner.Error {
|
||||
return &scanner.Error{
|
||||
Pos: t.fset.Position(t.pos),
|
||||
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.token, expected),
|
||||
Pos: t.fset.Position(t.curPos),
|
||||
Msg: fmt.Sprintf("unexpected token %s, expected %s", t.curToken, expected),
|
||||
}
|
||||
}
|
||||
|
||||
// tokenizer reads C source code and converts it to Go tokens.
|
||||
type tokenizer struct {
|
||||
pos token.Pos
|
||||
fset *token.FileSet
|
||||
token token.Token
|
||||
value string
|
||||
buf string
|
||||
curPos, peekPos token.Pos
|
||||
fset *token.FileSet
|
||||
curToken, peekToken token.Token
|
||||
curValue, peekValue string
|
||||
buf string
|
||||
}
|
||||
|
||||
// newTokenizer initializes a new tokenizer, positioned at the first token in
|
||||
// the string.
|
||||
func newTokenizer(start token.Pos, fset *token.FileSet, buf string) *tokenizer {
|
||||
t := &tokenizer{
|
||||
pos: start,
|
||||
fset: fset,
|
||||
buf: buf,
|
||||
token: token.ILLEGAL,
|
||||
peekPos: start,
|
||||
fset: fset,
|
||||
buf: buf,
|
||||
peekToken: token.ILLEGAL,
|
||||
}
|
||||
t.Next() // Parse the first token.
|
||||
// Parse the first two tokens (cur and peek).
|
||||
t.Next()
|
||||
t.Next()
|
||||
return t
|
||||
}
|
||||
|
||||
// Next consumes the next token in the stream. There is no return value, read
|
||||
// the next token from the pos, token and value properties.
|
||||
func (t *tokenizer) Next() {
|
||||
t.pos += token.Pos(len(t.value))
|
||||
// The previous peek is now the current token.
|
||||
t.curPos = t.peekPos
|
||||
t.curToken = t.peekToken
|
||||
t.curValue = t.peekValue
|
||||
|
||||
// Parse the next peek token.
|
||||
t.peekPos += token.Pos(len(t.curValue))
|
||||
for {
|
||||
if len(t.buf) == 0 {
|
||||
t.token = token.EOF
|
||||
t.peekToken = token.EOF
|
||||
return
|
||||
}
|
||||
c := t.buf[0]
|
||||
@@ -118,17 +197,28 @@ func (t *tokenizer) Next() {
|
||||
// Skip whitespace.
|
||||
// Based on this source, not sure whether it represents C whitespace:
|
||||
// https://en.cppreference.com/w/cpp/string/byte/isspace
|
||||
t.pos++
|
||||
t.peekPos++
|
||||
t.buf = t.buf[1:]
|
||||
case c == '(' || c == ')':
|
||||
case c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%':
|
||||
// Single-character tokens.
|
||||
// TODO: ++ (increment) and -- (decrement) operators.
|
||||
switch c {
|
||||
case '(':
|
||||
t.token = token.LPAREN
|
||||
t.peekToken = token.LPAREN
|
||||
case ')':
|
||||
t.token = token.RPAREN
|
||||
t.peekToken = token.RPAREN
|
||||
case '+':
|
||||
t.peekToken = token.ADD
|
||||
case '-':
|
||||
t.peekToken = token.SUB
|
||||
case '*':
|
||||
t.peekToken = token.MUL
|
||||
case '/':
|
||||
t.peekToken = token.QUO
|
||||
case '%':
|
||||
t.peekToken = token.REM
|
||||
}
|
||||
t.value = t.buf[:1]
|
||||
t.peekValue = t.buf[:1]
|
||||
t.buf = t.buf[1:]
|
||||
return
|
||||
case c >= '0' && c <= '9':
|
||||
@@ -146,17 +236,17 @@ func (t *tokenizer) Next() {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.peekValue = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
if hasDot {
|
||||
// Integer constants are more complicated than this but this is
|
||||
// a close approximation.
|
||||
// https://en.cppreference.com/w/cpp/language/integer_literal
|
||||
t.token = token.FLOAT
|
||||
t.value = strings.TrimRight(t.value, "f")
|
||||
t.peekToken = token.FLOAT
|
||||
t.peekValue = strings.TrimRight(t.peekValue, "f")
|
||||
} else {
|
||||
t.token = token.INT
|
||||
t.value = strings.TrimRight(t.value, "uUlL")
|
||||
t.peekToken = token.INT
|
||||
t.peekValue = strings.TrimRight(t.peekValue, "uUlL")
|
||||
}
|
||||
return
|
||||
case c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '_':
|
||||
@@ -170,9 +260,9 @@ func (t *tokenizer) Next() {
|
||||
break
|
||||
}
|
||||
}
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.peekValue = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
t.token = token.IDENT
|
||||
t.peekToken = token.IDENT
|
||||
return
|
||||
case c == '"':
|
||||
// String constant. Find the first '"' character that is not
|
||||
@@ -188,8 +278,8 @@ func (t *tokenizer) Next() {
|
||||
escape = c == '\\'
|
||||
}
|
||||
}
|
||||
t.token = token.STRING
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.peekToken = token.STRING
|
||||
t.peekValue = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
return
|
||||
case c == '\'':
|
||||
@@ -206,12 +296,12 @@ func (t *tokenizer) Next() {
|
||||
escape = c == '\\'
|
||||
}
|
||||
}
|
||||
t.token = token.CHAR
|
||||
t.value = t.buf[:tokenLen]
|
||||
t.peekToken = token.CHAR
|
||||
t.peekValue = t.buf[:tokenLen]
|
||||
t.buf = t.buf[tokenLen:]
|
||||
return
|
||||
default:
|
||||
t.token = token.ILLEGAL
|
||||
t.peekToken = token.ILLEGAL
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
+19
-2
@@ -18,7 +18,7 @@ func TestParseConst(t *testing.T) {
|
||||
{`(5)`, `(5)`},
|
||||
{`(((5)))`, `(5)`},
|
||||
{`)`, `error: 1:1: unexpected token )`},
|
||||
{`5)`, `error: 1:2: unexpected token )`},
|
||||
{`5)`, `error: 1:2: unexpected token ), expected end of expression`},
|
||||
{" \t)", `error: 1:4: unexpected token )`},
|
||||
{`5.8f`, `5.8`},
|
||||
{`foo`, `C.foo`},
|
||||
@@ -30,7 +30,24 @@ func TestParseConst(t *testing.T) {
|
||||
{`'a'`, `'a'`},
|
||||
{`0b10`, `0b10`},
|
||||
{`0x1234_5678`, `0x1234_5678`},
|
||||
{`5 5`, `error: 1:3: unexpected token INT`}, // test for a bugfix
|
||||
{`5 5`, `error: 1:3: unexpected token INT, expected end of expression`}, // test for a bugfix
|
||||
// Binary operators.
|
||||
{`5+5`, `5 + 5`},
|
||||
{`5-5`, `5 - 5`},
|
||||
{`5*5`, `5 * 5`},
|
||||
{`5/5`, `5 / 5`},
|
||||
{`5%5`, `5 % 5`},
|
||||
{`(5/5)`, `(5 / 5)`},
|
||||
{`1 - 2`, `1 - 2`},
|
||||
{`1 - 2 + 3`, `1 - 2 + 3`},
|
||||
{`1 - 2 * 3`, `1 - 2*3`},
|
||||
{`(1 - 2) * 3`, `(1 - 2) * 3`},
|
||||
{`1 * 2 - 3`, `1*2 - 3`},
|
||||
{`1 * (2 - 3)`, `1 * (2 - 3)`},
|
||||
// Unary operators.
|
||||
{`-5`, `-5`},
|
||||
{`-5-2`, `-5 - 2`},
|
||||
{`5 - - 2`, `5 - -2`},
|
||||
} {
|
||||
fset := token.NewFileSet()
|
||||
startPos := fset.AddFile("", -1, 1000).Pos(0)
|
||||
|
||||
+83
-12
@@ -4,6 +4,7 @@ package cgo
|
||||
// modification. It does not touch the AST itself.
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/scanner"
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-11-dev
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -56,6 +57,7 @@ unsigned tinygo_clang_Cursor_isBitField(GoCXCursor c);
|
||||
int tinygo_clang_globals_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
int tinygo_clang_struct_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
int tinygo_clang_enum_visitor(GoCXCursor c, GoCXCursor parent, CXClientData client_data);
|
||||
void tinygo_clang_inclusion_visitor(CXFile included_file, CXSourceLocation *inclusion_stack, unsigned include_len, CXClientData client_data);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
@@ -70,17 +72,14 @@ var diagnosticSeverity = [...]string{
|
||||
C.CXDiagnostic_Fatal: "fatal",
|
||||
}
|
||||
|
||||
func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename string, posLine int) {
|
||||
func (p *cgoPackage) parseFragment(fragment string, cflags []string, filename string) {
|
||||
index := C.clang_createIndex(0, 0)
|
||||
defer C.clang_disposeIndex(index)
|
||||
|
||||
// pretend to be a .c file
|
||||
filenameC := C.CString(posFilename + "!cgo.c")
|
||||
filenameC := C.CString(filename + "!cgo.c")
|
||||
defer C.free(unsafe.Pointer(filenameC))
|
||||
|
||||
// fix up error locations
|
||||
fragment = fmt.Sprintf("# %d %#v\n", posLine+1, posFilename) + fragment
|
||||
|
||||
fragmentC := C.CString(fragment)
|
||||
defer C.free(unsafe.Pointer(fragmentC))
|
||||
|
||||
@@ -114,6 +113,7 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
|
||||
}
|
||||
defer C.clang_disposeTranslationUnit(unit)
|
||||
|
||||
// Report parser and type errors.
|
||||
if numDiagnostics := int(C.clang_getNumDiagnostics(unit)); numDiagnostics != 0 {
|
||||
addDiagnostic := func(diagnostic C.CXDiagnostic) {
|
||||
spelling := getString(C.clang_getDiagnosticSpelling(diagnostic))
|
||||
@@ -134,10 +134,36 @@ func (p *cgoPackage) parseFragment(fragment string, cflags []string, posFilename
|
||||
}
|
||||
}
|
||||
|
||||
// Extract information required by CGo.
|
||||
ref := storedRefs.Put(p)
|
||||
defer storedRefs.Remove(ref)
|
||||
cursor := C.tinygo_clang_getTranslationUnitCursor(unit)
|
||||
C.tinygo_clang_visitChildren(cursor, C.CXCursorVisitor(C.tinygo_clang_globals_visitor), C.CXClientData(ref))
|
||||
|
||||
// Determine files read during CGo processing, for caching.
|
||||
inclusionCallback := func(includedFile C.CXFile) {
|
||||
// Get full file path.
|
||||
path := getString(C.clang_getFileName(includedFile))
|
||||
|
||||
// Get contents of file (that should be in-memory).
|
||||
size := C.size_t(0)
|
||||
rawData := C.clang_getFileContents(unit, includedFile, &size)
|
||||
if rawData == nil {
|
||||
// Sanity check. This should (hopefully) never trigger.
|
||||
panic("libclang: file contents was not loaded")
|
||||
}
|
||||
data := (*[1 << 24]byte)(unsafe.Pointer(rawData))[:size]
|
||||
|
||||
// Hash the contents if it isn't hashed yet.
|
||||
if _, ok := p.visitedFiles[path]; !ok {
|
||||
// already stored
|
||||
sum := sha512.Sum512_224(data)
|
||||
p.visitedFiles[path] = sum[:]
|
||||
}
|
||||
}
|
||||
inclusionCallbackRef := storedRefs.Put(inclusionCallback)
|
||||
defer storedRefs.Remove(inclusionCallbackRef)
|
||||
C.clang_getInclusions(unit, C.CXInclusionVisitor(C.tinygo_clang_inclusion_visitor), C.CXClientData(inclusionCallbackRef))
|
||||
}
|
||||
|
||||
//export tinygo_clang_globals_visitor
|
||||
@@ -167,14 +193,14 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
||||
}
|
||||
fn.args = append(fn.args, paramInfo{
|
||||
name: argName,
|
||||
typeExpr: p.makeASTType(argType, pos),
|
||||
typeExpr: p.makeDecayingASTType(argType, pos),
|
||||
})
|
||||
}
|
||||
resultType := C.tinygo_clang_getCursorResultType(c)
|
||||
if resultType.kind != C.CXType_Void {
|
||||
fn.results = &ast.FieldList{
|
||||
List: []*ast.Field{
|
||||
&ast.Field{
|
||||
{
|
||||
Type: p.makeASTType(resultType, pos),
|
||||
},
|
||||
},
|
||||
@@ -351,7 +377,7 @@ func (p *cgoPackage) addErrorAfter(pos token.Pos, after, msg string) {
|
||||
func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
|
||||
if filepath.IsAbs(position.Filename) {
|
||||
// Relative paths for readability, like other Go parser errors.
|
||||
relpath, err := filepath.Rel(p.dir, position.Filename)
|
||||
relpath, err := filepath.Rel(p.currentDir, position.Filename)
|
||||
if err == nil {
|
||||
position.Filename = relpath
|
||||
}
|
||||
@@ -362,6 +388,41 @@ func (p *cgoPackage) addErrorAt(position token.Position, msg string) {
|
||||
})
|
||||
}
|
||||
|
||||
// makeDecayingASTType does the same as makeASTType but takes care of decaying
|
||||
// types (arrays in function parameters, etc). It is otherwise identical to
|
||||
// makeASTType.
|
||||
func (p *cgoPackage) makeDecayingASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
// Strip typedefs, if any.
|
||||
underlyingType := typ
|
||||
if underlyingType.kind == C.CXType_Typedef {
|
||||
c := C.tinygo_clang_getTypeDeclaration(typ)
|
||||
underlyingType = C.tinygo_clang_getTypedefDeclUnderlyingType(c)
|
||||
// TODO: support a chain of typedefs. At the moment, it seems to get
|
||||
// stuck in an endless loop when trying to get to the most underlying
|
||||
// type.
|
||||
}
|
||||
// Check for decaying type. An example would be an array type in a
|
||||
// parameter. This declaration:
|
||||
// void foo(char buf[6]);
|
||||
// is the same as this one:
|
||||
// void foo(char *buf);
|
||||
// But this one:
|
||||
// void bar(char buf[6][4]);
|
||||
// equals this:
|
||||
// void bar(char *buf[4]);
|
||||
// so not all array dimensions should be stripped, just the first one.
|
||||
// TODO: there are more kinds of decaying types.
|
||||
if underlyingType.kind == C.CXType_ConstantArray {
|
||||
// Apply type decaying.
|
||||
pointeeType := C.clang_getElementType(underlyingType)
|
||||
return &ast.StarExpr{
|
||||
Star: pos,
|
||||
X: p.makeASTType(pointeeType, pos),
|
||||
}
|
||||
}
|
||||
return p.makeASTType(typ, pos)
|
||||
}
|
||||
|
||||
// makeASTType return the ast.Expr for the given libclang type. In other words,
|
||||
// it converts a libclang type to a type in the Go AST.
|
||||
func (p *cgoPackage) makeASTType(typ C.CXType, pos token.Pos) ast.Expr {
|
||||
@@ -746,7 +807,7 @@ func tinygo_clang_struct_visitor(c, parent C.GoCXCursor, client_data C.CXClientD
|
||||
}
|
||||
*inBitfield = false
|
||||
field.Names = []*ast.Ident{
|
||||
&ast.Ident{
|
||||
{
|
||||
NamePos: pos,
|
||||
Name: name,
|
||||
Obj: &ast.Object{
|
||||
@@ -767,8 +828,18 @@ func tinygo_clang_enum_visitor(c, parent C.GoCXCursor, client_data C.CXClientDat
|
||||
pos := p.getCursorPosition(c)
|
||||
value := C.tinygo_clang_getEnumConstantDeclValue(c)
|
||||
p.constants[name] = constantInfo{
|
||||
expr: &ast.BasicLit{pos, token.INT, strconv.FormatInt(int64(value), 10)},
|
||||
pos: pos,
|
||||
expr: &ast.BasicLit{
|
||||
ValuePos: pos,
|
||||
Kind: token.INT,
|
||||
Value: strconv.FormatInt(int64(value), 10),
|
||||
},
|
||||
pos: pos,
|
||||
}
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
//export tinygo_clang_inclusion_visitor
|
||||
func tinygo_clang_inclusion_visitor(includedFile C.CXFile, inclusionStack *C.CXSourceLocation, includeLen C.unsigned, clientData C.CXClientData) {
|
||||
callback := storedRefs.Get(unsafe.Pointer(clientData)).(func(C.CXFile))
|
||||
callback(includedFile)
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// +build !byollvm
|
||||
// +build !llvm10
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-11/include
|
||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@11/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm11/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-11/lib -lclang
|
||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@11/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm11/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
@@ -1,14 +0,0 @@
|
||||
// +build !byollvm
|
||||
// +build llvm10
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-10/include
|
||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@10/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm10/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-10/lib -lclang
|
||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@10/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm10/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
@@ -0,0 +1,16 @@
|
||||
//go:build !byollvm && llvm11
|
||||
// +build !byollvm,llvm11
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-11/include
|
||||
#cgo darwin amd64 CFLAGS: -I/usr/local/opt/llvm@11/include
|
||||
#cgo darwin arm64 CFLAGS: -I/opt/homebrew/opt/llvm@11/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm11/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-11/lib -lclang
|
||||
#cgo darwin amd64 LDFLAGS: -L/usr/local/opt/llvm@11/lib -lclang -lffi
|
||||
#cgo darwin arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@11/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm11/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
@@ -0,0 +1,16 @@
|
||||
//go:build !byollvm && llvm12
|
||||
// +build !byollvm,llvm12
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-12/include
|
||||
#cgo darwin amd64 CFLAGS: -I/usr/local/opt/llvm@12/include
|
||||
#cgo darwin arm64 CFLAGS: -I/opt/homebrew/opt/llvm@12/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm12/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-12/lib -lclang
|
||||
#cgo darwin amd64 LDFLAGS: -L/usr/local/opt/llvm@12/lib -lclang -lffi
|
||||
#cgo darwin arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@12/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm12/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
@@ -0,0 +1,16 @@
|
||||
//go:build !byollvm && !llvm11 && !llvm12
|
||||
// +build !byollvm,!llvm11,!llvm12
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-13/include
|
||||
#cgo darwin amd64 CFLAGS: -I/usr/local/opt/llvm@13/include
|
||||
#cgo darwin arm64 CFLAGS: -I/opt/homebrew/opt/llvm@13/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm13/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-13/lib -lclang
|
||||
#cgo darwin amd64 LDFLAGS: -L/usr/local/opt/llvm@13/lib -lclang -lffi
|
||||
#cgo darwin arm64 LDFLAGS: -L/opt/homebrew/opt/llvm@13/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm13/lib -lclang
|
||||
*/
|
||||
import "C"
|
||||
@@ -3,7 +3,7 @@
|
||||
// are slightly different from the ones defined in libclang.go, but they
|
||||
// should be ABI compatible.
|
||||
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-11-dev
|
||||
|
||||
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
|
||||
return clang_getTranslationUnitCursor(tu);
|
||||
|
||||
Vendored
+20
@@ -4,6 +4,26 @@ import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
//go:linkname C.CString runtime.cgo_CString
|
||||
func C.CString(string) *C.char
|
||||
|
||||
//go:linkname C.GoString runtime.cgo_GoString
|
||||
func C.GoString(*C.char) string
|
||||
|
||||
//go:linkname C.__GoStringN runtime.cgo_GoStringN
|
||||
func C.__GoStringN(*C.char, uintptr) string
|
||||
|
||||
func C.GoStringN(cstr *C.char, length C.int) string {
|
||||
return C.__GoStringN(cstr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__GoBytes runtime.cgo_GoBytes
|
||||
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
|
||||
|
||||
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
type C.int16_t = int16
|
||||
type C.int32_t = int32
|
||||
type C.int64_t = int64
|
||||
|
||||
Vendored
+20
@@ -4,6 +4,26 @@ import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
//go:linkname C.CString runtime.cgo_CString
|
||||
func C.CString(string) *C.char
|
||||
|
||||
//go:linkname C.GoString runtime.cgo_GoString
|
||||
func C.GoString(*C.char) string
|
||||
|
||||
//go:linkname C.__GoStringN runtime.cgo_GoStringN
|
||||
func C.__GoStringN(*C.char, uintptr) string
|
||||
|
||||
func C.GoStringN(cstr *C.char, length C.int) string {
|
||||
return C.__GoStringN(cstr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__GoBytes runtime.cgo_GoBytes
|
||||
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
|
||||
|
||||
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
const C.bar = C.foo
|
||||
const C.foo = 3
|
||||
|
||||
|
||||
Vendored
+8
@@ -14,6 +14,12 @@ typedef someType noType; // undefined type
|
||||
#define SOME_CONST_2 6) // const not used (so no error)
|
||||
#define SOME_CONST_3 1234 // const too large for byte
|
||||
*/
|
||||
//
|
||||
//
|
||||
// #define SOME_CONST_4 8) // after some empty lines
|
||||
import "C"
|
||||
|
||||
// #warning another warning
|
||||
import "C"
|
||||
|
||||
// Make sure that errors for the following lines won't change with future
|
||||
@@ -30,4 +36,6 @@ var (
|
||||
_ = C.SOME_CONST_1
|
||||
|
||||
_ byte = C.SOME_CONST_3
|
||||
|
||||
_ = C.SOME_CONST_4
|
||||
)
|
||||
|
||||
Vendored
+24
-1
@@ -1,13 +1,16 @@
|
||||
// CGo errors:
|
||||
// testdata/errors.go:4:2: warning: some warning
|
||||
// testdata/errors.go:11:9: error: unknown type name 'someType'
|
||||
// testdata/errors.go:13:23: unexpected token )
|
||||
// testdata/errors.go:22:5: warning: another warning
|
||||
// testdata/errors.go:13:23: unexpected token ), expected end of expression
|
||||
// testdata/errors.go:19:26: unexpected token ), expected end of expression
|
||||
|
||||
// Type checking errors after CGo processing:
|
||||
// testdata/errors.go:102: cannot use 2 << 10 (untyped int constant 2048) as uint8 value in variable declaration (overflows)
|
||||
// testdata/errors.go:105: unknown field z in struct literal
|
||||
// testdata/errors.go:108: undeclared name: C.SOME_CONST_1
|
||||
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
|
||||
// testdata/errors.go:112: undeclared name: C.SOME_CONST_4
|
||||
|
||||
package main
|
||||
|
||||
@@ -15,6 +18,26 @@ import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
//go:linkname C.CString runtime.cgo_CString
|
||||
func C.CString(string) *C.char
|
||||
|
||||
//go:linkname C.GoString runtime.cgo_GoString
|
||||
func C.GoString(*C.char) string
|
||||
|
||||
//go:linkname C.__GoStringN runtime.cgo_GoStringN
|
||||
func C.__GoStringN(*C.char, uintptr) string
|
||||
|
||||
func C.GoStringN(cstr *C.char, length C.int) string {
|
||||
return C.__GoStringN(cstr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__GoBytes runtime.cgo_GoBytes
|
||||
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
|
||||
|
||||
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
const C.SOME_CONST_3 = 1234
|
||||
|
||||
type C.int16_t = int16
|
||||
|
||||
Vendored
+20
@@ -9,6 +9,26 @@ import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
//go:linkname C.CString runtime.cgo_CString
|
||||
func C.CString(string) *C.char
|
||||
|
||||
//go:linkname C.GoString runtime.cgo_GoString
|
||||
func C.GoString(*C.char) string
|
||||
|
||||
//go:linkname C.__GoStringN runtime.cgo_GoStringN
|
||||
func C.__GoStringN(*C.char, uintptr) string
|
||||
|
||||
func C.GoStringN(cstr *C.char, length C.int) string {
|
||||
return C.__GoStringN(cstr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__GoBytes runtime.cgo_GoBytes
|
||||
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
|
||||
|
||||
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
const C.BAR = 3
|
||||
const C.FOO_H = 1
|
||||
|
||||
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
// Function signatures.
|
||||
int foo(int a, int b);
|
||||
void variadic0();
|
||||
void variadic2(int x, int y, ...);
|
||||
|
||||
// Global variable signatures.
|
||||
extern int someValue;
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// Test function signatures.
|
||||
func accessFunctions() {
|
||||
C.foo(3, 4)
|
||||
C.variadic0()
|
||||
C.variadic2(3, 5)
|
||||
}
|
||||
|
||||
func accessGlobals() {
|
||||
_ = C.someValue
|
||||
}
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
package main
|
||||
|
||||
import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
//go:linkname C.CString runtime.cgo_CString
|
||||
func C.CString(string) *C.char
|
||||
|
||||
//go:linkname C.GoString runtime.cgo_GoString
|
||||
func C.GoString(*C.char) string
|
||||
|
||||
//go:linkname C.__GoStringN runtime.cgo_GoStringN
|
||||
func C.__GoStringN(*C.char, uintptr) string
|
||||
|
||||
func C.GoStringN(cstr *C.char, length C.int) string {
|
||||
return C.__GoStringN(cstr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__GoBytes runtime.cgo_GoBytes
|
||||
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
|
||||
|
||||
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
//export foo
|
||||
func C.foo(a C.int, b C.int) C.int
|
||||
|
||||
//export variadic0
|
||||
//go:variadic
|
||||
func C.variadic0()
|
||||
|
||||
//export variadic2
|
||||
//go:variadic
|
||||
func C.variadic2(x C.int, y C.int)
|
||||
|
||||
var C.foo$funcaddr unsafe.Pointer
|
||||
var C.variadic0$funcaddr unsafe.Pointer
|
||||
var C.variadic2$funcaddr unsafe.Pointer
|
||||
|
||||
//go:extern someValue
|
||||
var C.someValue C.int
|
||||
|
||||
type C.int16_t = int16
|
||||
type C.int32_t = int32
|
||||
type C.int64_t = int64
|
||||
type C.int8_t = int8
|
||||
type C.uint16_t = uint16
|
||||
type C.uint32_t = uint32
|
||||
type C.uint64_t = uint64
|
||||
type C.uint8_t = uint8
|
||||
type C.uintptr_t = uintptr
|
||||
type C.char uint8
|
||||
type C.int int32
|
||||
type C.long int32
|
||||
type C.longlong int64
|
||||
type C.schar int8
|
||||
type C.short int16
|
||||
type C.uchar uint8
|
||||
type C.uint uint32
|
||||
type C.ulong uint32
|
||||
type C.ulonglong uint64
|
||||
type C.ushort uint16
|
||||
Vendored
+4
-10
@@ -104,13 +104,13 @@ typedef struct {
|
||||
unsigned char e : 3;
|
||||
// Note that C++ allows bitfields bigger than the underlying type.
|
||||
} bitfield_t;
|
||||
|
||||
// Function signatures.
|
||||
void variadic0();
|
||||
void variadic2(int x, int y, ...);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// // Test that we can refer from this CGo fragment to the fragment above.
|
||||
// typedef myint myint2;
|
||||
import "C"
|
||||
|
||||
var (
|
||||
// Simple typedefs.
|
||||
_ C.myint
|
||||
@@ -167,9 +167,3 @@ func accessUnion() {
|
||||
var _ *C.int = union2d.unionfield_i()
|
||||
var _ *[2]float64 = union2d.unionfield_d()
|
||||
}
|
||||
|
||||
// Test function signatures.
|
||||
func accessFunctions() {
|
||||
C.variadic0()
|
||||
C.variadic2(3, 5)
|
||||
}
|
||||
|
||||
Vendored
+24
-19
@@ -4,10 +4,25 @@ import "unsafe"
|
||||
|
||||
var _ unsafe.Pointer
|
||||
|
||||
func C.variadic0() //go:variadic
|
||||
func C.variadic2(x C.int, y C.int) //go:variadic
|
||||
var C.variadic0$funcaddr unsafe.Pointer
|
||||
var C.variadic2$funcaddr unsafe.Pointer
|
||||
//go:linkname C.CString runtime.cgo_CString
|
||||
func C.CString(string) *C.char
|
||||
|
||||
//go:linkname C.GoString runtime.cgo_GoString
|
||||
func C.GoString(*C.char) string
|
||||
|
||||
//go:linkname C.__GoStringN runtime.cgo_GoStringN
|
||||
func C.__GoStringN(*C.char, uintptr) string
|
||||
|
||||
func C.GoStringN(cstr *C.char, length C.int) string {
|
||||
return C.__GoStringN(cstr, uintptr(length))
|
||||
}
|
||||
|
||||
//go:linkname C.__GoBytes runtime.cgo_GoBytes
|
||||
func C.__GoBytes(unsafe.Pointer, uintptr) []byte
|
||||
|
||||
func C.GoBytes(ptr unsafe.Pointer, length C.int) []byte {
|
||||
return C.__GoBytes(ptr, uintptr(length))
|
||||
}
|
||||
|
||||
const C.option2A = 20
|
||||
const C.optionA = 0
|
||||
@@ -67,9 +82,7 @@ type C.union3_t = C.union_1
|
||||
type C.union_nested_t = C.union_3
|
||||
type C.unionarray_t = struct{ arr [10]C.uchar }
|
||||
|
||||
func (s *C.struct_4) bitfield_a() C.uchar {
|
||||
return s.__bitfield_1 & 0x1f
|
||||
}
|
||||
func (s *C.struct_4) bitfield_a() C.uchar { return s.__bitfield_1 & 0x1f }
|
||||
func (s *C.struct_4) set_bitfield_a(value C.uchar) {
|
||||
s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0
|
||||
}
|
||||
@@ -105,15 +118,9 @@ type C.struct_type1 struct {
|
||||
}
|
||||
type C.struct_type2 struct{ _type C.int }
|
||||
|
||||
func (union *C.union_1) unionfield_i() *C.int {
|
||||
return (*C.int)(unsafe.Pointer(&union.$union))
|
||||
}
|
||||
func (union *C.union_1) unionfield_d() *float64 {
|
||||
return (*float64)(unsafe.Pointer(&union.$union))
|
||||
}
|
||||
func (union *C.union_1) unionfield_s() *C.short {
|
||||
return (*C.short)(unsafe.Pointer(&union.$union))
|
||||
}
|
||||
func (union *C.union_1) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_1) unionfield_d() *float64 { return (*float64)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_1) unionfield_s() *C.short { return (*C.short)(unsafe.Pointer(&union.$union)) }
|
||||
|
||||
type C.union_1 struct{ $union uint64 }
|
||||
|
||||
@@ -138,9 +145,7 @@ func (union *C.union_3) unionfield_thing() *C.union3_t {
|
||||
|
||||
type C.union_3 struct{ $union [2]uint64 }
|
||||
|
||||
func (union *C.union_union2d) unionfield_i() *C.int {
|
||||
return (*C.int)(unsafe.Pointer(&union.$union))
|
||||
}
|
||||
func (union *C.union_union2d) unionfield_i() *C.int { return (*C.int)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_union2d) unionfield_d() *[2]float64 {
|
||||
return (*[2]float64)(unsafe.Pointer(&union.$union))
|
||||
}
|
||||
|
||||
+173
-48
@@ -5,6 +5,7 @@ package compileopts
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
@@ -21,7 +22,7 @@ type Config struct {
|
||||
TestConfig TestConfig
|
||||
}
|
||||
|
||||
// Triple returns the LLVM target triple, like armv6m-none-eabi.
|
||||
// Triple returns the LLVM target triple, like armv6m-unknown-unknown-eabi.
|
||||
func (c *Config) Triple() string {
|
||||
return c.Target.Triple
|
||||
}
|
||||
@@ -33,10 +34,16 @@ func (c *Config) CPU() string {
|
||||
}
|
||||
|
||||
// Features returns a list of features this CPU supports. For example, for a
|
||||
// RISC-V processor, that could be ["+a", "+c", "+m"]. For many targets, an
|
||||
// empty list will be returned.
|
||||
func (c *Config) Features() []string {
|
||||
return c.Target.Features
|
||||
// RISC-V processor, that could be "+a,+c,+m". For many targets, an empty list
|
||||
// will be returned.
|
||||
func (c *Config) Features() string {
|
||||
if c.Target.Features == "" {
|
||||
return c.Options.LLVMFeatures
|
||||
}
|
||||
if c.Options.LLVMFeatures == "" {
|
||||
return c.Target.Features
|
||||
}
|
||||
return c.Target.Features + "," + c.Options.LLVMFeatures
|
||||
}
|
||||
|
||||
// GOOS returns the GOOS of the target. This might not always be the actual OS:
|
||||
@@ -53,9 +60,15 @@ func (c *Config) GOARCH() string {
|
||||
return c.Target.GOARCH
|
||||
}
|
||||
|
||||
// GOARM will return the GOARM environment variable given to the compiler when
|
||||
// building a program.
|
||||
func (c *Config) GOARM() string {
|
||||
return c.Options.GOARM
|
||||
}
|
||||
|
||||
// BuildTags returns the complete list of build tags used during this build.
|
||||
func (c *Config) BuildTags() []string {
|
||||
tags := append(c.Target.BuildTags, []string{"tinygo", "gc." + c.GC(), "scheduler." + c.Scheduler()}...)
|
||||
tags := append(c.Target.BuildTags, []string{"tinygo", "math_big_pure_go", "gc." + c.GC(), "scheduler." + c.Scheduler(), "serial." + c.Serial()}...)
|
||||
for i := 1; i <= c.GoMinorVersion; i++ {
|
||||
tags = append(tags, fmt.Sprintf("go1.%d", i))
|
||||
}
|
||||
@@ -72,7 +85,7 @@ func (c *Config) CgoEnabled() bool {
|
||||
}
|
||||
|
||||
// GC returns the garbage collection strategy in use on this platform. Valid
|
||||
// values are "none", "leaking", "extalloc", and "conservative".
|
||||
// values are "none", "leaking", and "conservative".
|
||||
func (c *Config) GC() string {
|
||||
if c.Options.GC != "" {
|
||||
return c.Options.GC
|
||||
@@ -80,21 +93,16 @@ func (c *Config) GC() string {
|
||||
if c.Target.GC != "" {
|
||||
return c.Target.GC
|
||||
}
|
||||
for _, tag := range c.Target.BuildTags {
|
||||
if tag == "baremetal" || tag == "wasm" {
|
||||
return "conservative"
|
||||
}
|
||||
}
|
||||
return "extalloc"
|
||||
return "conservative"
|
||||
}
|
||||
|
||||
// NeedsStackObjects returns true if the compiler should insert stack objects
|
||||
// that can be traced by the garbage collector.
|
||||
func (c *Config) NeedsStackObjects() bool {
|
||||
switch c.GC() {
|
||||
case "conservative", "extalloc":
|
||||
case "conservative":
|
||||
for _, tag := range c.BuildTags() {
|
||||
if tag == "wasm" {
|
||||
if tag == "tinygo.wasm" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -106,7 +114,7 @@ func (c *Config) NeedsStackObjects() bool {
|
||||
}
|
||||
|
||||
// Scheduler returns the scheduler implementation. Valid values are "none",
|
||||
//"coroutines" and "tasks".
|
||||
// "asyncify" and "tasks".
|
||||
func (c *Config) Scheduler() string {
|
||||
if c.Options.Scheduler != "" {
|
||||
return c.Options.Scheduler
|
||||
@@ -114,32 +122,40 @@ func (c *Config) Scheduler() string {
|
||||
if c.Target.Scheduler != "" {
|
||||
return c.Target.Scheduler
|
||||
}
|
||||
// Fall back to coroutines, which are supported everywhere.
|
||||
return "coroutines"
|
||||
// Fall back to none.
|
||||
return "none"
|
||||
}
|
||||
|
||||
// FuncImplementation picks an appropriate func value implementation for the
|
||||
// target.
|
||||
func (c *Config) FuncImplementation() string {
|
||||
switch c.Scheduler() {
|
||||
case "tasks":
|
||||
// A func value is implemented as a pair of pointers:
|
||||
// {context, function pointer}
|
||||
// where the context may be a pointer to a heap-allocated struct
|
||||
// containing the free variables, or it may be undef if the function
|
||||
// being pointed to doesn't need a context. The function pointer is a
|
||||
// regular function pointer.
|
||||
return "doubleword"
|
||||
case "none", "coroutines":
|
||||
// As "doubleword", but with the function pointer replaced by a unique
|
||||
// ID per function signature. Function values are called by using a
|
||||
// switch statement and choosing which function to call.
|
||||
// Pick the switch implementation with the coroutines scheduler, as it
|
||||
// allows the use of blocking inside a function that is used as a func
|
||||
// value.
|
||||
return "switch"
|
||||
// Serial returns the serial implementation for this build configuration: uart,
|
||||
// usb (meaning USB-CDC), or none.
|
||||
func (c *Config) Serial() string {
|
||||
if c.Options.Serial != "" {
|
||||
return c.Options.Serial
|
||||
}
|
||||
if c.Target.Serial != "" {
|
||||
return c.Target.Serial
|
||||
}
|
||||
return "none"
|
||||
}
|
||||
|
||||
// OptLevels returns the optimization level (0-2), size level (0-2), and inliner
|
||||
// threshold as used in the LLVM optimization pipeline.
|
||||
func (c *Config) OptLevels() (optLevel, sizeLevel int, inlinerThreshold uint) {
|
||||
switch c.Options.Opt {
|
||||
case "none", "0":
|
||||
return 0, 0, 0 // -O0
|
||||
case "1":
|
||||
return 1, 0, 0 // -O1
|
||||
case "2":
|
||||
return 2, 0, 225 // -O2
|
||||
case "s":
|
||||
return 2, 1, 225 // -Os
|
||||
case "z":
|
||||
return 2, 2, 5 // -Oz, default
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
// This is not shown to the user: valid choices are already checked as
|
||||
// part of Options.Verify(). It is here as a sanity check.
|
||||
panic("unknown optimization level: -opt=" + c.Options.Opt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,20 +176,111 @@ func (c *Config) AutomaticStackSize() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
|
||||
// calculates and patches in the checksum for the 2nd stage bootloader.
|
||||
func (c *Config) RP2040BootPatch() bool {
|
||||
if c.Target.RP2040BootPatch != nil {
|
||||
return *c.Target.RP2040BootPatch
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MuslArchitecture returns the architecture name as used in musl libc. It is
|
||||
// usually the same as the first part of the LLVM triple, but not always.
|
||||
func MuslArchitecture(triple string) string {
|
||||
arch := strings.Split(triple, "-")[0]
|
||||
if strings.HasPrefix(arch, "arm") || strings.HasPrefix(arch, "thumb") {
|
||||
arch = "arm"
|
||||
}
|
||||
return arch
|
||||
}
|
||||
|
||||
// LibcPath returns the path to the libc directory. The libc path will be either
|
||||
// a precompiled libc shipped with a TinyGo build, or a libc path in the cache
|
||||
// directory (which might not yet be built).
|
||||
func (c *Config) LibcPath(name string) (path string, precompiled bool) {
|
||||
archname := c.Triple()
|
||||
if c.CPU() != "" {
|
||||
archname += "-" + c.CPU()
|
||||
}
|
||||
|
||||
// Try to load a precompiled library.
|
||||
precompiledDir := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", archname, name)
|
||||
if _, err := os.Stat(precompiledDir); err == nil {
|
||||
// Found a precompiled library for this OS/architecture. Return the path
|
||||
// directly.
|
||||
return precompiledDir, true
|
||||
}
|
||||
|
||||
// No precompiled library found. Determine the path name that will be used
|
||||
// in the build cache.
|
||||
return filepath.Join(goenv.Get("GOCACHE"), name+"-"+archname), false
|
||||
}
|
||||
|
||||
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
|
||||
// preprocessing.
|
||||
func (c *Config) CFlags() []string {
|
||||
cflags := append([]string{}, c.Options.CFlags...)
|
||||
var cflags []string
|
||||
for _, flag := range c.Target.CFlags {
|
||||
cflags = append(cflags, strings.ReplaceAll(flag, "{root}", goenv.Get("TINYGOROOT")))
|
||||
}
|
||||
if c.Target.Libc == "picolibc" {
|
||||
switch c.Target.Libc {
|
||||
case "picolibc":
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
cflags = append(cflags, "-nostdlibinc", "-Xclang", "-internal-isystem", "-Xclang", filepath.Join(root, "lib", "picolibc", "newlib", "libc", "include"))
|
||||
cflags = append(cflags, "-I"+filepath.Join(root, "lib/picolibc-include"))
|
||||
picolibcDir := filepath.Join(root, "lib", "picolibc", "newlib", "libc")
|
||||
path, _ := c.LibcPath("picolibc")
|
||||
cflags = append(cflags,
|
||||
"--sysroot="+path,
|
||||
"-isystem", filepath.Join(picolibcDir, "include"),
|
||||
"-isystem", filepath.Join(picolibcDir, "tinystdio"),
|
||||
)
|
||||
case "musl":
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
path, _ := c.LibcPath("musl")
|
||||
arch := MuslArchitecture(c.Triple())
|
||||
cflags = append(cflags,
|
||||
"-nostdlibinc",
|
||||
"-isystem", filepath.Join(path, "include"),
|
||||
"-isystem", filepath.Join(root, "lib", "musl", "arch", arch),
|
||||
"-isystem", filepath.Join(root, "lib", "musl", "include"),
|
||||
)
|
||||
case "wasi-libc":
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
cflags = append(cflags, "--sysroot="+root+"/lib/wasi-libc/sysroot")
|
||||
case "mingw-w64":
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
path, _ := c.LibcPath("mingw-w64")
|
||||
cflags = append(cflags,
|
||||
"--sysroot="+path,
|
||||
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "crt"),
|
||||
"-isystem", filepath.Join(root, "lib", "mingw-w64", "mingw-w64-headers", "defaults", "include"),
|
||||
"-D_UCRT",
|
||||
)
|
||||
case "":
|
||||
// No libc specified, nothing to add.
|
||||
default:
|
||||
// Incorrect configuration. This could be handled in a better way, but
|
||||
// usually this will be found by developers (not by TinyGo users).
|
||||
panic("unknown libc: " + c.Target.Libc)
|
||||
}
|
||||
if c.Debug() {
|
||||
cflags = append(cflags, "-g")
|
||||
// Always emit debug information. It is optionally stripped at link time.
|
||||
cflags = append(cflags, "-g")
|
||||
// Use the same optimization level as TinyGo.
|
||||
cflags = append(cflags, "-O"+c.Options.Opt)
|
||||
// Set the LLVM target triple.
|
||||
cflags = append(cflags, "--target="+c.Triple())
|
||||
// Set the -mcpu (or similar) flag.
|
||||
if c.Target.CPU != "" {
|
||||
if c.GOARCH() == "amd64" || c.GOARCH() == "386" {
|
||||
// x86 prefers the -march flag (-mcpu is deprecated there).
|
||||
cflags = append(cflags, "-march="+c.Target.CPU)
|
||||
} else if strings.HasPrefix(c.Triple(), "avr") {
|
||||
// AVR MCUs use -mmcu instead of -mcpu.
|
||||
cflags = append(cflags, "-mmcu="+c.Target.CPU)
|
||||
} else {
|
||||
// The rest just uses -mcpu.
|
||||
cflags = append(cflags, "-mcpu="+c.Target.CPU)
|
||||
}
|
||||
}
|
||||
return cflags
|
||||
}
|
||||
@@ -184,7 +291,7 @@ func (c *Config) CFlags() []string {
|
||||
func (c *Config) LDFlags() []string {
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
// Merge and adjust LDFlags.
|
||||
ldflags := append([]string{}, c.Options.LDFlags...)
|
||||
var ldflags []string
|
||||
for _, flag := range c.Target.LDFlags {
|
||||
ldflags = append(ldflags, strings.ReplaceAll(flag, "{root}", root))
|
||||
}
|
||||
@@ -213,8 +320,9 @@ func (c *Config) VerifyIR() bool {
|
||||
return c.Options.VerifyIR
|
||||
}
|
||||
|
||||
// Debug returns whether to add debug symbols to the IR, for debugging with GDB
|
||||
// and similar.
|
||||
// Debug returns whether debug (DWARF) information should be retained by the
|
||||
// linker. By default, debug information is retained but it can be removed with
|
||||
// the -no-debug flag.
|
||||
func (c *Config) Debug() bool {
|
||||
return c.Options.Debug
|
||||
}
|
||||
@@ -238,6 +346,11 @@ func (c *Config) BinaryFormat(ext string) string {
|
||||
// More information:
|
||||
// https://github.com/Microsoft/uf2
|
||||
return "uf2"
|
||||
case ".zip":
|
||||
if c.Target.BinaryFormat != "" {
|
||||
return c.Target.BinaryFormat
|
||||
}
|
||||
return "zip"
|
||||
default:
|
||||
// Use the ELF format for unrecognized file formats.
|
||||
return "elf"
|
||||
@@ -255,6 +368,9 @@ func (c *Config) Programmer() (method, openocdInterface string) {
|
||||
case "openocd", "msd", "command":
|
||||
// The -programmer flag only specifies the flash method.
|
||||
return c.Options.Programmer, c.Target.OpenOCDInterface
|
||||
case "bmp":
|
||||
// The -programmer flag only specifies the flash method.
|
||||
return c.Options.Programmer, ""
|
||||
default:
|
||||
// The -programmer flag specifies something else, assume it specifies
|
||||
// the OpenOCD interface name.
|
||||
@@ -321,6 +437,15 @@ func (c *Config) WasmAbi() string {
|
||||
return c.Target.WasmAbi
|
||||
}
|
||||
|
||||
// Emulator returns the emulator target config
|
||||
func (c *Config) Emulator() []string {
|
||||
var emulator []string
|
||||
for _, s := range c.Target.Emulator {
|
||||
emulator = append(emulator, strings.ReplaceAll(s, "{root}", goenv.Get("TINYGOROOT")))
|
||||
}
|
||||
return emulator
|
||||
}
|
||||
|
||||
type TestConfig struct {
|
||||
CompileTestBinary bool
|
||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
||||
|
||||
+48
-21
@@ -2,37 +2,49 @@ package compileopts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
|
||||
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
|
||||
validGCOptions = []string{"none", "leaking", "conservative"}
|
||||
validSchedulerOptions = []string{"none", "tasks", "asyncify"}
|
||||
validSerialOptions = []string{"none", "uart", "usb"}
|
||||
validPrintSizeOptions = []string{"none", "short", "full"}
|
||||
validPanicStrategyOptions = []string{"print", "trap"}
|
||||
validOptOptions = []string{"none", "0", "1", "2", "s", "z"}
|
||||
)
|
||||
|
||||
// Options contains extra options to give to the compiler. These options are
|
||||
// usually passed from the command line.
|
||||
// usually passed from the command line, but can also be passed in environment
|
||||
// variables for example.
|
||||
type Options struct {
|
||||
Target string
|
||||
Opt string
|
||||
GC string
|
||||
PanicStrategy string
|
||||
Scheduler string
|
||||
PrintIR bool
|
||||
DumpSSA bool
|
||||
VerifyIR bool
|
||||
PrintCommands bool
|
||||
Debug bool
|
||||
PrintSizes string
|
||||
PrintStacks bool
|
||||
CFlags []string
|
||||
LDFlags []string
|
||||
Tags string
|
||||
WasmAbi string
|
||||
TestConfig TestConfig
|
||||
Programmer string
|
||||
GOOS string // environment variable
|
||||
GOARCH string // environment variable
|
||||
GOARM string // environment variable (only used with GOARCH=arm)
|
||||
Target string
|
||||
Opt string
|
||||
GC string
|
||||
PanicStrategy string
|
||||
Scheduler string
|
||||
Serial string
|
||||
PrintIR bool
|
||||
DumpSSA bool
|
||||
VerifyIR bool
|
||||
PrintCommands func(cmd string, args ...string)
|
||||
Semaphore chan struct{} // -p flag controls cap
|
||||
Debug bool
|
||||
PrintSizes string
|
||||
PrintAllocs *regexp.Regexp // regexp string
|
||||
PrintStacks bool
|
||||
Tags string
|
||||
WasmAbi string
|
||||
GlobalValues map[string]map[string]string // map[pkgpath]map[varname]value
|
||||
TestConfig TestConfig
|
||||
Programmer string
|
||||
OpenOCDCommands []string
|
||||
LLVMFeatures string
|
||||
Directory string
|
||||
}
|
||||
|
||||
// Verify performs a validation on the given options, raising an error if options are not valid.
|
||||
@@ -55,6 +67,15 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
}
|
||||
|
||||
if o.Serial != "" {
|
||||
valid := isInArray(validSerialOptions, o.Serial)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid serial option '%s': valid values are %s`,
|
||||
o.Serial,
|
||||
strings.Join(validSerialOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
if o.PrintSizes != "" {
|
||||
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
|
||||
if !valid {
|
||||
@@ -73,6 +94,12 @@ func (o *Options) Verify() error {
|
||||
}
|
||||
}
|
||||
|
||||
if o.Opt != "" {
|
||||
if !isInArray(validOptOptions, o.Opt) {
|
||||
return fmt.Errorf("invalid -opt=%s: valid values are %s", o.Opt, strings.Join(validOptOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
|
||||
func TestVerifyOptions(t *testing.T) {
|
||||
|
||||
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, extalloc, conservative`)
|
||||
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, coroutines`)
|
||||
expectedGCError := errors.New(`invalid gc option 'incorrect': valid values are none, leaking, conservative`)
|
||||
expectedSchedulerError := errors.New(`invalid scheduler option 'incorrect': valid values are none, tasks, asyncify`)
|
||||
expectedPrintSizeError := errors.New(`invalid size option 'incorrect': valid values are none, short, full`)
|
||||
expectedPanicStrategyError := errors.New(`invalid panic option 'incorrect': valid values are print, trap`)
|
||||
|
||||
@@ -42,12 +42,6 @@ func TestVerifyOptions(t *testing.T) {
|
||||
GC: "leaking",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GCOptionExtalloc",
|
||||
opts: compileopts.Options{
|
||||
GC: "extalloc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GCOptionConservative",
|
||||
opts: compileopts.Options{
|
||||
@@ -73,12 +67,6 @@ func TestVerifyOptions(t *testing.T) {
|
||||
Scheduler: "tasks",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SchedulerOptionCoroutines",
|
||||
opts: compileopts.Options{
|
||||
Scheduler: "coroutines",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "InvalidPrintSizeOption",
|
||||
opts: compileopts.Options{
|
||||
|
||||
+155
-81
@@ -5,14 +5,18 @@ package compileopts
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Target specification for a given target. Used for bare metal targets.
|
||||
@@ -24,13 +28,13 @@ type TargetSpec struct {
|
||||
Inherits []string `json:"inherits"`
|
||||
Triple string `json:"llvm-target"`
|
||||
CPU string `json:"cpu"`
|
||||
Features []string `json:"features"`
|
||||
Features string `json:"features"`
|
||||
GOOS string `json:"goos"`
|
||||
GOARCH string `json:"goarch"`
|
||||
BuildTags []string `json:"build-tags"`
|
||||
GC string `json:"gc"`
|
||||
Scheduler string `json:"scheduler"`
|
||||
Compiler string `json:"compiler"`
|
||||
Serial string `json:"serial"` // which serial output to use (uart, usb, none)
|
||||
Linker string `json:"linker"`
|
||||
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
||||
Libc string `json:"libc"`
|
||||
@@ -40,10 +44,12 @@ type TargetSpec struct {
|
||||
LDFlags []string `json:"ldflags"`
|
||||
LinkerScript string `json:"linkerscript"`
|
||||
ExtraFiles []string `json:"extra-files"`
|
||||
RP2040BootPatch *bool `json:"rp2040-boot-patch"` // Patch RP2040 2nd stage bootloader checksum
|
||||
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
|
||||
FlashCommand string `json:"flash-command"`
|
||||
GDB string `json:"gdb"`
|
||||
GDB []string `json:"gdb"`
|
||||
PortReset string `json:"flash-1200-bps-reset"`
|
||||
SerialPort []string `json:"serial-port"` // serial port IDs in the form "acm:vid:pid" or "usb:vid:pid"
|
||||
FlashMethod string `json:"flash-method"`
|
||||
FlashVolume string `json:"msd-volume-name"`
|
||||
FlashFilename string `json:"msd-firmware-name"`
|
||||
@@ -91,7 +97,7 @@ func (spec *TargetSpec) overrideProperties(child *TargetSpec) {
|
||||
dst.Set(src)
|
||||
case "append", "":
|
||||
// or append the field of child to spec
|
||||
dst.Set(reflect.AppendSlice(src, dst))
|
||||
dst.Set(reflect.AppendSlice(dst, src))
|
||||
default:
|
||||
panic("override mode must be 'copy' or 'append' (default). I don't know how to '" + tag + "'.")
|
||||
}
|
||||
@@ -158,116 +164,184 @@ func (spec *TargetSpec) resolveInherits() error {
|
||||
}
|
||||
|
||||
// Load a target specification.
|
||||
func LoadTarget(target string) (*TargetSpec, error) {
|
||||
if target == "" {
|
||||
func LoadTarget(options *Options) (*TargetSpec, error) {
|
||||
if options.Target == "" {
|
||||
// Configure based on GOOS/GOARCH environment variables (falling back to
|
||||
// runtime.GOOS/runtime.GOARCH), and generate a LLVM target based on it.
|
||||
goos := goenv.Get("GOOS")
|
||||
goarch := goenv.Get("GOARCH")
|
||||
llvmos := goos
|
||||
llvmarch := map[string]string{
|
||||
"386": "i386",
|
||||
"amd64": "x86_64",
|
||||
"arm64": "aarch64",
|
||||
}[goarch]
|
||||
if llvmarch == "" {
|
||||
llvmarch = goarch
|
||||
var llvmarch string
|
||||
switch options.GOARCH {
|
||||
case "386":
|
||||
llvmarch = "i386"
|
||||
case "amd64":
|
||||
llvmarch = "x86_64"
|
||||
case "arm64":
|
||||
llvmarch = "aarch64"
|
||||
case "arm":
|
||||
switch options.GOARM {
|
||||
case "5":
|
||||
llvmarch = "armv5"
|
||||
case "6":
|
||||
llvmarch = "armv6"
|
||||
case "7":
|
||||
llvmarch = "armv7"
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid GOARM=%s, must be 5, 6, or 7", options.GOARM)
|
||||
}
|
||||
default:
|
||||
llvmarch = options.GOARCH
|
||||
}
|
||||
target = llvmarch + "--" + llvmos
|
||||
if goarch == "arm" {
|
||||
llvmos := options.GOOS
|
||||
if llvmos == "darwin" {
|
||||
// Use macosx* instead of darwin, otherwise darwin/arm64 will refer
|
||||
// to iOS!
|
||||
llvmos = "macosx10.12.0"
|
||||
if llvmarch == "aarch64" {
|
||||
// Looks like Apple prefers to call this architecture ARM64
|
||||
// instead of AArch64.
|
||||
llvmarch = "arm64"
|
||||
}
|
||||
}
|
||||
// Target triples (which actually have four components, but are called
|
||||
// triples for historical reasons) have the form:
|
||||
// arch-vendor-os-environment
|
||||
target := llvmarch + "-unknown-" + llvmos
|
||||
if options.GOARCH == "arm" {
|
||||
target += "-gnueabihf"
|
||||
}
|
||||
return defaultTarget(goos, goarch, target)
|
||||
if options.GOOS == "windows" {
|
||||
target += "-gnu"
|
||||
}
|
||||
return defaultTarget(options.GOOS, options.GOARCH, target)
|
||||
}
|
||||
|
||||
// See whether there is a target specification for this target (e.g.
|
||||
// Arduino).
|
||||
spec := &TargetSpec{}
|
||||
err := spec.loadFromGivenStr(target)
|
||||
if err == nil {
|
||||
// Successfully loaded this target from a built-in .json file. Make sure
|
||||
// it includes all parents as specified in the "inherits" key.
|
||||
err = spec.resolveInherits()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return spec, nil
|
||||
} else if !os.IsNotExist(err) {
|
||||
// Expected a 'file not found' error, got something else. Report it as
|
||||
// an error.
|
||||
err := spec.loadFromGivenStr(options.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
// Load target from given triple, ignore GOOS/GOARCH environment
|
||||
// variables.
|
||||
tripleSplit := strings.Split(target, "-")
|
||||
if len(tripleSplit) < 3 {
|
||||
return nil, errors.New("expected a full LLVM target or a custom target in -target flag")
|
||||
}
|
||||
if tripleSplit[0] == "arm" {
|
||||
// LLVM and Clang have a different idea of what "arm" means, so
|
||||
// upgrade to a slightly more modern ARM. In fact, when you pass
|
||||
// --target=arm--linux-gnueabihf to Clang, it will convert that
|
||||
// internally to armv7-unknown-linux-gnueabihf. Changing the
|
||||
// architecture to armv7 will keep things consistent.
|
||||
tripleSplit[0] = "armv7"
|
||||
}
|
||||
goos := tripleSplit[2]
|
||||
if strings.HasPrefix(goos, "darwin") {
|
||||
goos = "darwin"
|
||||
}
|
||||
goarch := map[string]string{ // map from LLVM arch to Go arch
|
||||
"i386": "386",
|
||||
"i686": "386",
|
||||
"x86_64": "amd64",
|
||||
"aarch64": "arm64",
|
||||
"armv7": "arm",
|
||||
}[tripleSplit[0]]
|
||||
if goarch == "" {
|
||||
goarch = tripleSplit[0]
|
||||
}
|
||||
return defaultTarget(goos, goarch, strings.Join(tripleSplit, "-"))
|
||||
}
|
||||
// Successfully loaded this target from a built-in .json file. Make sure
|
||||
// it includes all parents as specified in the "inherits" key.
|
||||
err = spec.resolveInherits()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if spec.Scheduler == "asyncify" {
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_asyncify_wasm.S")
|
||||
}
|
||||
|
||||
return spec, nil
|
||||
}
|
||||
|
||||
func defaultTarget(goos, goarch, triple string) (*TargetSpec, error) {
|
||||
// No target spec available. Use the default one, useful on most systems
|
||||
// with a regular OS.
|
||||
spec := TargetSpec{
|
||||
Triple: triple,
|
||||
GOOS: goos,
|
||||
GOARCH: goarch,
|
||||
BuildTags: []string{goos, goarch},
|
||||
Compiler: "clang",
|
||||
Linker: "cc",
|
||||
CFlags: []string{"--target=" + triple},
|
||||
GDB: "gdb",
|
||||
PortReset: "false",
|
||||
Triple: triple,
|
||||
GOOS: goos,
|
||||
GOARCH: goarch,
|
||||
BuildTags: []string{goos, goarch},
|
||||
Scheduler: "tasks",
|
||||
Linker: "cc",
|
||||
DefaultStackSize: 1024 * 64, // 64kB
|
||||
GDB: []string{"gdb"},
|
||||
PortReset: "false",
|
||||
}
|
||||
switch goarch {
|
||||
case "386":
|
||||
spec.CPU = "pentium4"
|
||||
spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
|
||||
case "amd64":
|
||||
spec.CPU = "x86-64"
|
||||
spec.Features = "+cx8,+fxsr,+mmx,+sse,+sse2,+x87"
|
||||
case "arm":
|
||||
spec.CPU = "generic"
|
||||
spec.CFlags = append(spec.CFlags, "-fno-unwind-tables")
|
||||
switch strings.Split(triple, "-")[0] {
|
||||
case "armv5":
|
||||
spec.Features = "+armv5t,+strict-align,-aes,-bf16,-d32,-dotprod,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fp64,-fpregs,-fullfp16,-mve.fp,-neon,-sha2,-thumb-mode,-vfp2,-vfp2sp,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
||||
case "armv6":
|
||||
spec.Features = "+armv6,+dsp,+fp64,+strict-align,+vfp2,+vfp2sp,-aes,-d32,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-neon,-sha2,-thumb-mode,-vfp3,-vfp3d16,-vfp3d16sp,-vfp3sp,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
||||
case "armv7":
|
||||
spec.Features = "+armv7-a,+d32,+dsp,+fp64,+neon,+vfp2,+vfp2sp,+vfp3,+vfp3d16,+vfp3d16sp,+vfp3sp,-aes,-fp-armv8,-fp-armv8d16,-fp-armv8d16sp,-fp-armv8sp,-fp16,-fp16fml,-fullfp16,-sha2,-thumb-mode,-vfp4,-vfp4d16,-vfp4d16sp,-vfp4sp"
|
||||
}
|
||||
case "arm64":
|
||||
spec.CPU = "generic"
|
||||
spec.Features = "+neon"
|
||||
}
|
||||
if goos == "darwin" {
|
||||
spec.CFlags = append(spec.CFlags, "-isysroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk")
|
||||
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
|
||||
} else if goos == "linux" {
|
||||
spec.Linker = "ld.lld"
|
||||
spec.RTLib = "compiler-rt"
|
||||
spec.Libc = "musl"
|
||||
spec.LDFlags = append(spec.LDFlags, "--gc-sections")
|
||||
} else if goos == "windows" {
|
||||
spec.Linker = "ld.lld"
|
||||
spec.Libc = "mingw-w64"
|
||||
// Note: using a medium code model, low image base and no ASLR
|
||||
// because Go doesn't really need those features. ASLR patches
|
||||
// around issues for unsafe languages like C/C++ that are not
|
||||
// normally present in Go (without explicitly opting in).
|
||||
// For more discussion:
|
||||
// https://groups.google.com/g/Golang-nuts/c/Jd9tlNc6jUE/m/Zo-7zIP_m3MJ?pli=1
|
||||
spec.LDFlags = append(spec.LDFlags,
|
||||
"-m", "i386pep",
|
||||
"-Bdynamic",
|
||||
"--image-base", "0x400000",
|
||||
"--gc-sections",
|
||||
"--no-insert-timestamp",
|
||||
)
|
||||
llvmMajor, _ := strconv.Atoi(strings.Split(llvm.Version, ".")[0])
|
||||
if llvmMajor >= 12 {
|
||||
// This flag was added in LLVM 12. At the same time, LLVM 12
|
||||
// switched the default from --dynamicbase to --no-dynamicbase.
|
||||
spec.LDFlags = append(spec.LDFlags, "--no-dynamicbase")
|
||||
}
|
||||
} else {
|
||||
spec.LDFlags = append(spec.LDFlags, "-no-pie", "-Wl,--gc-sections") // WARNING: clang < 5.0 requires -nopie
|
||||
}
|
||||
if goarch != "wasm" {
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+".S")
|
||||
suffix := ""
|
||||
if goos == "windows" {
|
||||
// Windows uses a different calling convention from other operating
|
||||
// systems so we need separate assembly files.
|
||||
suffix = "_windows"
|
||||
}
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/runtime/gc_"+goarch+suffix+".S")
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, "src/internal/task/task_stack_"+goarch+suffix+".S")
|
||||
}
|
||||
if goarch != runtime.GOARCH {
|
||||
// Some educated guesses as to how to invoke helper programs.
|
||||
spec.GDB = "gdb-multiarch"
|
||||
spec.GDB = []string{"gdb-multiarch"}
|
||||
if goarch == "arm" && goos == "linux" {
|
||||
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
|
||||
spec.Linker = "arm-linux-gnueabihf-gcc"
|
||||
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
|
||||
spec.Emulator = []string{"qemu-arm"}
|
||||
}
|
||||
if goarch == "arm64" && goos == "linux" {
|
||||
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
|
||||
spec.Linker = "aarch64-linux-gnu-gcc"
|
||||
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
|
||||
spec.Emulator = []string{"qemu-aarch64"}
|
||||
}
|
||||
if goarch == "386" && runtime.GOARCH == "amd64" {
|
||||
spec.CFlags = append(spec.CFlags, "-m32")
|
||||
spec.LDFlags = append(spec.LDFlags, "-m32")
|
||||
}
|
||||
if goos != runtime.GOOS {
|
||||
if goos == "windows" {
|
||||
spec.Emulator = []string{"wine"}
|
||||
}
|
||||
}
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
// LookupGDB looks up a gdb executable.
|
||||
func (spec *TargetSpec) LookupGDB() (string, error) {
|
||||
if len(spec.GDB) == 0 {
|
||||
return "", errors.New("gdb not configured in the target specification")
|
||||
}
|
||||
for _, d := range spec.GDB {
|
||||
_, err := exec.LookPath(d)
|
||||
if err == nil {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New("no gdb found configured in the target specification (" + strings.Join(spec.GDB, ", ") + ")")
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
package compileopts
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadTarget(t *testing.T) {
|
||||
_, err := LoadTarget("arduino")
|
||||
_, err := LoadTarget(&Options{Target: "arduino"})
|
||||
if err != nil {
|
||||
t.Error("LoadTarget test failed:", err)
|
||||
}
|
||||
|
||||
_, err = LoadTarget("notexist")
|
||||
_, err = LoadTarget(&Options{Target: "notexist"})
|
||||
if err == nil {
|
||||
t.Error("LoadTarget should have failed with non existing target")
|
||||
}
|
||||
|
||||
if err.Error() != "expected a full LLVM target or a custom target in -target flag" {
|
||||
if !os.IsNotExist(err) {
|
||||
t.Error("LoadTarget failed for wrong reason:", err)
|
||||
}
|
||||
}
|
||||
@@ -26,7 +27,7 @@ func TestOverrideProperties(t *testing.T) {
|
||||
base := &TargetSpec{
|
||||
GOOS: "baseGoos",
|
||||
CPU: "baseCpu",
|
||||
Features: []string{"bf1", "bf2"},
|
||||
CFlags: []string{"-base-foo", "-base-bar"},
|
||||
BuildTags: []string{"bt1", "bt2"},
|
||||
Emulator: []string{"be1", "be2"},
|
||||
DefaultStackSize: 42,
|
||||
@@ -36,7 +37,7 @@ func TestOverrideProperties(t *testing.T) {
|
||||
child := &TargetSpec{
|
||||
GOOS: "",
|
||||
CPU: "chlidCpu",
|
||||
Features: []string{"cf1", "cf2"},
|
||||
CFlags: []string{"-child-foo", "-child-bar"},
|
||||
Emulator: []string{"ce1", "ce2"},
|
||||
AutoStackSize: &childAutoStackSize,
|
||||
DefaultStackSize: 64,
|
||||
@@ -50,8 +51,8 @@ func TestOverrideProperties(t *testing.T) {
|
||||
if base.CPU != "chlidCpu" {
|
||||
t.Errorf("Overriding failed : got %v", base.CPU)
|
||||
}
|
||||
if !reflect.DeepEqual(base.Features, []string{"cf1", "cf2", "bf1", "bf2"}) {
|
||||
t.Errorf("Overriding failed : got %v", base.Features)
|
||||
if !reflect.DeepEqual(base.CFlags, []string{"-base-foo", "-base-bar", "-child-foo", "-child-bar"}) {
|
||||
t.Errorf("Overriding failed : got %v", base.CFlags)
|
||||
}
|
||||
if !reflect.DeepEqual(base.BuildTags, []string{"bt1", "bt2"}) {
|
||||
t.Errorf("Overriding failed : got %v", base.BuildTags)
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package compiler
|
||||
|
||||
// This file defines alias functions for functions that are normally defined in
|
||||
// Go assembly.
|
||||
//
|
||||
// The Go toolchain defines many performance critical functions in assembly
|
||||
// instead of plain Go. This is a problem for TinyGo as it currently (as of
|
||||
// august 2021) is not able to compile these assembly files and even if it
|
||||
// could, it would not be able to make use of them for many targets that are
|
||||
// supported by TinyGo (baremetal RISC-V, AVR, etc). Therefore, many of these
|
||||
// functions are aliased to their generic Go implementation.
|
||||
// This results in slower than possible implementations, but at least they are
|
||||
// usable.
|
||||
|
||||
import "tinygo.org/x/go-llvm"
|
||||
|
||||
var stdlibAliases = map[string]string{
|
||||
// crypto packages
|
||||
"crypto/md5.block": "crypto/md5.blockGeneric",
|
||||
"crypto/sha1.block": "crypto/sha1.blockGeneric",
|
||||
"crypto/sha1.blockAMD64": "crypto/sha1.blockGeneric",
|
||||
"crypto/sha256.block": "crypto/sha256.blockGeneric",
|
||||
"crypto/sha512.blockAMD64": "crypto/sha512.blockGeneric",
|
||||
|
||||
// math package
|
||||
"math.Asin": "math.asin",
|
||||
"math.Asinh": "math.asinh",
|
||||
"math.Acos": "math.acos",
|
||||
"math.Acosh": "math.acosh",
|
||||
"math.Atan": "math.atan",
|
||||
"math.Atanh": "math.atanh",
|
||||
"math.Atan2": "math.atan2",
|
||||
"math.Cbrt": "math.cbrt",
|
||||
"math.Ceil": "math.ceil",
|
||||
"math.archCeil": "math.ceil",
|
||||
"math.Cos": "math.cos",
|
||||
"math.Cosh": "math.cosh",
|
||||
"math.Erf": "math.erf",
|
||||
"math.Erfc": "math.erfc",
|
||||
"math.Exp": "math.exp",
|
||||
"math.archExp": "math.exp",
|
||||
"math.Expm1": "math.expm1",
|
||||
"math.Exp2": "math.exp2",
|
||||
"math.archExp2": "math.exp2",
|
||||
"math.Floor": "math.floor",
|
||||
"math.archFloor": "math.floor",
|
||||
"math.Frexp": "math.frexp",
|
||||
"math.Hypot": "math.hypot",
|
||||
"math.archHypot": "math.hypot",
|
||||
"math.Ldexp": "math.ldexp",
|
||||
"math.Log": "math.log",
|
||||
"math.archLog": "math.log",
|
||||
"math.Log1p": "math.log1p",
|
||||
"math.Log10": "math.log10",
|
||||
"math.Log2": "math.log2",
|
||||
"math.Max": "math.max",
|
||||
"math.archMax": "math.max",
|
||||
"math.Min": "math.min",
|
||||
"math.archMin": "math.min",
|
||||
"math.Mod": "math.mod",
|
||||
"math.Modf": "math.modf",
|
||||
"math.archModf": "math.modf",
|
||||
"math.Pow": "math.pow",
|
||||
"math.Remainder": "math.remainder",
|
||||
"math.Sin": "math.sin",
|
||||
"math.Sinh": "math.sinh",
|
||||
"math.Sqrt": "math.sqrt",
|
||||
"math.archSqrt": "math.sqrt",
|
||||
"math.Tan": "math.tan",
|
||||
"math.Tanh": "math.tanh",
|
||||
"math.Trunc": "math.trunc",
|
||||
"math.archTrunc": "math.trunc",
|
||||
}
|
||||
|
||||
// createAlias implements the function (in the builder) as a call to the alias
|
||||
// function.
|
||||
func (b *builder) createAlias(alias llvm.Value) {
|
||||
b.llvmFn.SetVisibility(llvm.HiddenVisibility)
|
||||
b.llvmFn.SetUnnamedAddr(true)
|
||||
|
||||
if b.Debug {
|
||||
if b.fn.Syntax() != nil {
|
||||
// Create debug info file if present.
|
||||
b.difunc = b.attachDebugInfo(b.fn)
|
||||
}
|
||||
pos := b.program.Fset.Position(b.fn.Pos())
|
||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), b.difunc, llvm.Metadata{})
|
||||
}
|
||||
entryBlock := llvm.AddBasicBlock(b.llvmFn, "entry")
|
||||
b.SetInsertPointAtEnd(entryBlock)
|
||||
if b.llvmFn.Type() != alias.Type() {
|
||||
b.addError(b.fn.Pos(), "alias function should have the same type as aliasee "+alias.Name())
|
||||
b.CreateUnreachable()
|
||||
return
|
||||
}
|
||||
result := b.CreateCall(alias, b.llvmFn.Params(), "")
|
||||
if result.Type().TypeKind() == llvm.VoidTypeKind {
|
||||
b.CreateRetVoid()
|
||||
} else {
|
||||
b.CreateRet(result)
|
||||
}
|
||||
}
|
||||
+88
-44
@@ -15,22 +15,16 @@ import (
|
||||
// createLookupBoundsCheck emits a bounds check before doing a lookup into a
|
||||
// slice. This is required by the Go language spec: an index out of bounds must
|
||||
// cause a panic.
|
||||
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
|
||||
// The caller should make sure that index is at least as big as arrayLen.
|
||||
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value) {
|
||||
if b.info.nobounds {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
return
|
||||
}
|
||||
|
||||
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
|
||||
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
|
||||
// correctly extend that type.
|
||||
if indexType.Underlying().(*types.Basic).Info()&types.IsUnsigned == 0 {
|
||||
index = b.CreateZExt(index, arrayLen.Type(), "")
|
||||
} else {
|
||||
index = b.CreateSExt(index, arrayLen.Type(), "")
|
||||
}
|
||||
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
|
||||
// Extend arrayLen if it's too small.
|
||||
if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
|
||||
// The index is bigger than the array length type, so extend it.
|
||||
arrayLen = b.CreateZExt(arrayLen, index.Type(), "")
|
||||
}
|
||||
@@ -70,27 +64,9 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
|
||||
}
|
||||
|
||||
// Extend low and high to be the same size as capacity.
|
||||
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if lowType.Info()&types.IsUnsigned != 0 {
|
||||
low = b.CreateZExt(low, capacityType, "")
|
||||
} else {
|
||||
low = b.CreateSExt(low, capacityType, "")
|
||||
}
|
||||
}
|
||||
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if highType.Info()&types.IsUnsigned != 0 {
|
||||
high = b.CreateZExt(high, capacityType, "")
|
||||
} else {
|
||||
high = b.CreateSExt(high, capacityType, "")
|
||||
}
|
||||
}
|
||||
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if maxType.Info()&types.IsUnsigned != 0 {
|
||||
max = b.CreateZExt(max, capacityType, "")
|
||||
} else {
|
||||
max = b.CreateSExt(max, capacityType, "")
|
||||
}
|
||||
}
|
||||
low = b.extendInteger(low, lowType, capacityType)
|
||||
high = b.extendInteger(high, highType, capacityType)
|
||||
max = b.extendInteger(max, maxType, capacityType)
|
||||
|
||||
// Now do the bounds check: low > high || high > capacity
|
||||
outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||
@@ -101,6 +77,49 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
|
||||
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
|
||||
}
|
||||
|
||||
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
|
||||
// conversions. This conversion was added in Go 1.17. For details, see:
|
||||
// https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer
|
||||
func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen int64) {
|
||||
// From the spec:
|
||||
// > If the length of the slice is less than the length of the array, a
|
||||
// > run-time panic occurs.
|
||||
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
|
||||
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
|
||||
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
|
||||
}
|
||||
|
||||
// createUnsafeSliceCheck inserts a runtime check used for unsafe.Slice. This
|
||||
// function must panic if the ptr/len parameters are invalid.
|
||||
func (b *builder) createUnsafeSliceCheck(ptr, len llvm.Value, lenType *types.Basic) {
|
||||
// From the documentation of unsafe.Slice:
|
||||
// > At run time, if len is negative, or if ptr is nil and len is not
|
||||
// > zero, a run-time panic occurs.
|
||||
// However, in practice, it is also necessary to check that the length is
|
||||
// not too big that a GEP wouldn't be possible without wrapping the pointer.
|
||||
// These two checks (non-negative and not too big) can be merged into one
|
||||
// using an unsiged greater than.
|
||||
|
||||
// Make sure the len value is at least as big as a uintptr.
|
||||
len = b.extendInteger(len, lenType, b.uintptrType)
|
||||
|
||||
// Determine the maximum slice size, and therefore the maximum value of the
|
||||
// len parameter.
|
||||
maxSize := b.maxSliceSize(ptr.Type().ElementType())
|
||||
maxSizeValue := llvm.ConstInt(len.Type(), maxSize, false)
|
||||
|
||||
// Do the check. By using unsigned greater than for the length check, signed
|
||||
// negative values are also checked (which are very large numbers when
|
||||
// interpreted as signed values).
|
||||
zero := llvm.ConstInt(len.Type(), 0, false)
|
||||
lenOutOfBounds := b.CreateICmp(llvm.IntUGT, len, maxSizeValue, "")
|
||||
ptrIsNil := b.CreateICmp(llvm.IntEQ, ptr, llvm.ConstNull(ptr.Type()), "")
|
||||
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
|
||||
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
|
||||
assert = b.CreateOr(assert, lenOutOfBounds, "")
|
||||
b.createRuntimeAssert(assert, "unsafe.Slice", "unsafeSlicePanic")
|
||||
}
|
||||
|
||||
// createChanBoundsCheck creates a bounds check before creating a new channel to
|
||||
// check that the value is not too big for runtime.chanMake.
|
||||
func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value, bufSizeType *types.Basic, pos token.Pos) {
|
||||
@@ -110,19 +129,8 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
|
||||
return
|
||||
}
|
||||
|
||||
// Check whether the bufSize parameter must be cast to a wider integer for
|
||||
// comparison.
|
||||
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
|
||||
if bufSizeType.Info()&types.IsUnsigned != 0 {
|
||||
// Unsigned, so zero-extend to uint type.
|
||||
bufSizeType = types.Typ[types.Uint]
|
||||
bufSize = b.CreateZExt(bufSize, b.intType, "")
|
||||
} else {
|
||||
// Signed, so sign-extend to int type.
|
||||
bufSizeType = types.Typ[types.Int]
|
||||
bufSize = b.CreateSExt(bufSize, b.intType, "")
|
||||
}
|
||||
}
|
||||
// Make sure bufSize is at least as big as maxBufSize (an uintptr).
|
||||
bufSize = b.extendInteger(bufSize, bufSizeType, b.uintptrType)
|
||||
|
||||
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
|
||||
// uintptr if uintptrs were signed.
|
||||
@@ -159,6 +167,13 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
|
||||
}
|
||||
|
||||
switch inst := inst.(type) {
|
||||
case *ssa.Alloc:
|
||||
// An alloc is never nil.
|
||||
return
|
||||
case *ssa.FreeVar:
|
||||
// A free variable is allocated in a parent function and is thus never
|
||||
// nil.
|
||||
return
|
||||
case *ssa.IndexAddr:
|
||||
// This pointer is the result of an index operation into a slice or
|
||||
// array. Such slices/arrays are already bounds checked so the pointer
|
||||
@@ -199,6 +214,19 @@ func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
|
||||
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
|
||||
}
|
||||
|
||||
// createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic
|
||||
// will be emitted. This follows the Go specification which says that a divide
|
||||
// by zero must cause a run time panic.
|
||||
func (b *builder) createDivideByZeroCheck(y llvm.Value) {
|
||||
if b.info.nobounds {
|
||||
return
|
||||
}
|
||||
|
||||
// isZero = y == 0
|
||||
isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "")
|
||||
b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic")
|
||||
}
|
||||
|
||||
// createRuntimeAssert is a common function to create a new branch on an assert
|
||||
// bool, calling an assert func if the assert value is true (1).
|
||||
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
|
||||
@@ -227,3 +255,19 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
|
||||
// Ok: assert didn't trigger so continue normally.
|
||||
b.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
|
||||
// extendInteger extends the value to at least targetType using a zero or sign
|
||||
// extend. The resulting value is not truncated: it may still be bigger than
|
||||
// targetType.
|
||||
func (b *builder) extendInteger(value llvm.Value, valueType types.Type, targetType llvm.Type) llvm.Value {
|
||||
if value.Type().IntTypeWidth() < targetType.IntTypeWidth() {
|
||||
if valueType.Underlying().(*types.Basic).Info()&types.IsUnsigned != 0 {
|
||||
// Unsigned, so zero-extend to the target type.
|
||||
value = b.CreateZExt(value, targetType, "")
|
||||
} else {
|
||||
// Signed, so sign-extend to the target type.
|
||||
value = b.CreateSExt(value, targetType, "")
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
+34
-3
@@ -1,6 +1,7 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
@@ -16,6 +17,20 @@ func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
||||
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
|
||||
ptr := b.getValue(call.Args[0])
|
||||
val := b.getValue(call.Args[1])
|
||||
if strings.HasPrefix(b.Triple, "avr") {
|
||||
// AtomicRMW does not work on AVR as intended:
|
||||
// - There are some register allocation issues (fixed by https://reviews.llvm.org/D97127 which is not yet in a usable LLVM release)
|
||||
// - The result is the new value instead of the old value
|
||||
vType := val.Type()
|
||||
name := fmt.Sprintf("__sync_fetch_and_add_%d", vType.IntTypeWidth()/8)
|
||||
fn := b.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
fn = llvm.AddFunction(b.mod, name, llvm.FunctionType(vType, []llvm.Type{ptr.Type(), vType}, false))
|
||||
}
|
||||
oldVal := b.createCall(fn, []llvm.Value{ptr, val}, "")
|
||||
// Return the new value, not the original value returned.
|
||||
return b.CreateAdd(oldVal, val, ""), true
|
||||
}
|
||||
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpAdd, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
|
||||
// Return the new value, not the original value returned by atomicrmw.
|
||||
return b.CreateAdd(oldVal, val, ""), true
|
||||
@@ -38,11 +53,10 @@ func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
||||
old := b.getValue(call.Args[1])
|
||||
newVal := b.getValue(call.Args[2])
|
||||
if strings.HasSuffix(name, "64") {
|
||||
arch := strings.Split(b.Triple, "-")[0]
|
||||
if strings.HasPrefix(arch, "arm") && strings.HasSuffix(arch, "m") {
|
||||
if strings.HasPrefix(b.Triple, "thumb") {
|
||||
// Work around a bug in LLVM, at least LLVM 11:
|
||||
// https://reviews.llvm.org/D95891
|
||||
// Check for armv6m, armv7, armv7em, and perhaps others.
|
||||
// Check for thumbv6m, thumbv7, thumbv7em, and perhaps others.
|
||||
// See also: https://gcc.gnu.org/onlinedocs/gcc/_005f_005fsync-Builtins.html
|
||||
compareAndSwap := b.mod.NamedFunction("__sync_val_compare_and_swap_8")
|
||||
if compareAndSwap.IsNil() {
|
||||
@@ -74,6 +88,23 @@ func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
||||
case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer":
|
||||
ptr := b.getValue(call.Args[0])
|
||||
val := b.getValue(call.Args[1])
|
||||
if strings.HasPrefix(b.Triple, "avr") {
|
||||
// SelectionDAGBuilder is currently missing the "are unaligned atomics allowed" check for stores.
|
||||
vType := val.Type()
|
||||
isPointer := vType.TypeKind() == llvm.PointerTypeKind
|
||||
if isPointer {
|
||||
// libcalls only supports integers, so cast to an integer.
|
||||
vType = b.uintptrType
|
||||
val = b.CreatePtrToInt(val, vType, "")
|
||||
ptr = b.CreateBitCast(ptr, llvm.PointerType(vType, 0), "")
|
||||
}
|
||||
name := fmt.Sprintf("__atomic_store_%d", vType.IntTypeWidth()/8)
|
||||
fn := b.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
fn = llvm.AddFunction(b.mod, name, llvm.FunctionType(vType, []llvm.Type{ptr.Type(), vType, b.uintptrType}, false))
|
||||
}
|
||||
return b.createCall(fn, []llvm.Value{ptr, val, llvm.ConstInt(b.uintptrType, 5, false)}, ""), true
|
||||
}
|
||||
store := b.CreateStore(val, ptr)
|
||||
store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
|
||||
store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
|
||||
|
||||
+25
-14
@@ -40,8 +40,7 @@ func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name strin
|
||||
if llvmFn.IsNil() {
|
||||
panic("trying to call non-existent function: " + fn.RelString(nil))
|
||||
}
|
||||
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
|
||||
args = append(args, llvm.ConstPointerNull(b.i8ptrType)) // coroutine handle
|
||||
args = append(args, llvm.Undef(b.i8ptrType)) // unused context parameter
|
||||
return b.createCall(llvmFn, args, name)
|
||||
}
|
||||
|
||||
@@ -58,15 +57,15 @@ func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm
|
||||
|
||||
// Expand an argument type to a list that can be used in a function call
|
||||
// parameter list.
|
||||
func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
func (c *compilerContext) expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fieldInfos := flattenAggregateType(t, name, goType)
|
||||
fieldInfos := c.flattenAggregateType(t, name, goType)
|
||||
if len(fieldInfos) <= maxFieldsPerParam {
|
||||
// managed to expand this parameter
|
||||
return fieldInfos
|
||||
} else {
|
||||
// failed to lower
|
||||
}
|
||||
// failed to expand this parameter: too many fields
|
||||
}
|
||||
// TODO: split small arrays
|
||||
return []paramInfo{
|
||||
@@ -105,7 +104,7 @@ func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
||||
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
switch v.Type().TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fieldInfos := flattenAggregateType(v.Type(), "", nil)
|
||||
fieldInfos := b.flattenAggregateType(v.Type(), "", nil)
|
||||
if len(fieldInfos) <= maxFieldsPerParam {
|
||||
fields := b.flattenAggregate(v)
|
||||
if len(fields) != len(fieldInfos) {
|
||||
@@ -124,12 +123,15 @@ func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
|
||||
// Try to flatten a struct type to a list of types. Returns a 1-element slice
|
||||
// with the passed in type if this is not possible.
|
||||
func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
func (c *compilerContext) flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
typeFlags := getTypeFlags(goType)
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
|
||||
var paramInfos []paramInfo
|
||||
for i, subfield := range t.StructElementTypes() {
|
||||
if c.targetData.TypeAllocSize(subfield) == 0 {
|
||||
continue
|
||||
}
|
||||
suffix := strconv.Itoa(i)
|
||||
if goType != nil {
|
||||
// Try to come up with a good suffix for this struct field,
|
||||
@@ -152,7 +154,7 @@ func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramIn
|
||||
suffix = []string{"context", "funcptr"}[i]
|
||||
}
|
||||
}
|
||||
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
|
||||
subInfos := c.flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
|
||||
for i := range subInfos {
|
||||
subInfos[i].flags |= typeFlags
|
||||
}
|
||||
@@ -218,8 +220,11 @@ func extractSubfield(t types.Type, field int) types.Type {
|
||||
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := make([]uint64, 0, t.StructElementTypesCount())
|
||||
var fields []uint64
|
||||
for fieldIndex, field := range t.StructElementTypes() {
|
||||
if c.targetData.TypeAllocSize(field) == 0 {
|
||||
continue
|
||||
}
|
||||
suboffsets := c.flattenAggregateTypeOffsets(field)
|
||||
offset := c.targetData.ElementOffset(t, fieldIndex)
|
||||
for i := range suboffsets {
|
||||
@@ -238,8 +243,11 @@ func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
func (b *builder) flattenAggregate(v llvm.Value) []llvm.Value {
|
||||
switch v.Type().TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := make([]llvm.Value, 0, v.Type().StructElementTypesCount())
|
||||
for i := range v.Type().StructElementTypes() {
|
||||
var fields []llvm.Value
|
||||
for i, field := range v.Type().StructElementTypes() {
|
||||
if b.targetData.TypeAllocSize(field) == 0 {
|
||||
continue
|
||||
}
|
||||
subfield := b.CreateExtractValue(v, i, "")
|
||||
subfields := b.flattenAggregate(subfield)
|
||||
fields = append(fields, subfields...)
|
||||
@@ -266,10 +274,13 @@ func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Val
|
||||
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
flattened := flattenAggregateType(t, "", nil)
|
||||
flattened := b.flattenAggregateType(t, "", nil)
|
||||
if len(flattened) <= maxFieldsPerParam {
|
||||
value := llvm.ConstNull(t)
|
||||
for i, subtyp := range t.StructElementTypes() {
|
||||
if b.targetData.TypeAllocSize(subtyp) == 0 {
|
||||
continue
|
||||
}
|
||||
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
|
||||
fields = remaining
|
||||
value = b.CreateInsertValue(value, structField, i, "")
|
||||
|
||||
+28
-9
@@ -32,8 +32,15 @@ func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
|
||||
// store value-to-send
|
||||
valueType := b.getLLVMType(instr.X.Type())
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
||||
b.CreateStore(chanValue, valueAlloca)
|
||||
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
|
||||
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
|
||||
if isZeroSize {
|
||||
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
|
||||
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
|
||||
} else {
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
|
||||
b.CreateStore(chanValue, valueAlloca)
|
||||
}
|
||||
|
||||
// Allocate blockedlist buffer.
|
||||
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
|
||||
@@ -46,7 +53,9 @@ func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
||||
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
if !isZeroSize {
|
||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
}
|
||||
}
|
||||
|
||||
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||
@@ -56,7 +65,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
ch := b.getValue(unop.X)
|
||||
|
||||
// Allocate memory to receive into.
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
||||
isZeroSize := b.targetData.TypeAllocSize(valueType) == 0
|
||||
var valueAlloca, valueAllocaCast, valueAllocaSize llvm.Value
|
||||
if isZeroSize {
|
||||
valueAlloca = llvm.ConstNull(llvm.PointerType(valueType, 0))
|
||||
valueAllocaCast = llvm.ConstNull(b.i8ptrType)
|
||||
} else {
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize = b.createTemporaryAlloca(valueType, "chan.value")
|
||||
}
|
||||
|
||||
// Allocate blockedlist buffer.
|
||||
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
|
||||
@@ -64,9 +80,14 @@ func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
|
||||
// Do the receive.
|
||||
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
|
||||
received := b.CreateLoad(valueAlloca, "chan.received")
|
||||
var received llvm.Value
|
||||
if isZeroSize {
|
||||
received = llvm.ConstNull(valueType)
|
||||
} else {
|
||||
received = b.CreateLoad(valueAlloca, "chan.received")
|
||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
}
|
||||
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
|
||||
if unop.CommaOk {
|
||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
|
||||
@@ -116,7 +137,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
// determine the receive buffer size and alignment.
|
||||
recvbufSize := uint64(0)
|
||||
recvbufAlign := 0
|
||||
hasReceives := false
|
||||
var selectStates []llvm.Value
|
||||
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
|
||||
for _, state := range expr.States {
|
||||
@@ -133,7 +153,6 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
if align := b.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
|
||||
recvbufAlign = align
|
||||
}
|
||||
hasReceives = true
|
||||
case types.SendOnly:
|
||||
// Store this value in an alloca and put a pointer to this alloca
|
||||
// in the send state.
|
||||
@@ -150,7 +169,7 @@ func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
|
||||
// Create a receive buffer, where the received value will be stored.
|
||||
recvbuf := llvm.Undef(b.i8ptrType)
|
||||
if hasReceives {
|
||||
if recvbufSize != 0 {
|
||||
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
|
||||
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
|
||||
recvbufAlloca.SetAlignment(recvbufAlign)
|
||||
|
||||
+503
-277
File diff suppressed because it is too large
Load Diff
+114
-49
@@ -4,12 +4,12 @@ import (
|
||||
"flag"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -17,43 +17,105 @@ import (
|
||||
// Pass -update to go test to update the output of the test files.
|
||||
var flagUpdate = flag.Bool("update", false, "update tests based on test output")
|
||||
|
||||
type testCase struct {
|
||||
file string
|
||||
target string
|
||||
scheduler string
|
||||
}
|
||||
|
||||
// Basic tests for the compiler. Build some Go files and compare the output with
|
||||
// the expected LLVM IR for regression testing.
|
||||
func TestCompiler(t *testing.T) {
|
||||
target, err := compileopts.LoadTarget("i686--linux")
|
||||
t.Parallel()
|
||||
|
||||
// Check LLVM version.
|
||||
llvmMajor, err := strconv.Atoi(strings.SplitN(llvm.Version, ".", 2)[0])
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
t.Fatal("could not parse LLVM version:", llvm.Version)
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: &compileopts.Options{},
|
||||
Target: target,
|
||||
}
|
||||
compilerConfig := &Config{
|
||||
Triple: config.Triple(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
FuncImplementation: config.FuncImplementation(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
}
|
||||
machine, err := NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
if llvmMajor < 11 {
|
||||
// It is likely this version needs to be bumped in the future.
|
||||
// The goal is to at least test the LLVM version that's used by default
|
||||
// in TinyGo and (if possible without too many workarounds) also some
|
||||
// previous versions.
|
||||
t.Skip("compiler tests require LLVM 11 or above, got LLVM ", llvm.Version)
|
||||
}
|
||||
|
||||
tests := []string{
|
||||
"basic.go",
|
||||
"pointer.go",
|
||||
"slice.go",
|
||||
"float.go",
|
||||
// Determine Go minor version (e.g. 16 in go1.16.3).
|
||||
_, goMinor, err := goenv.GetGorootVersion(goenv.Get("GOROOT"))
|
||||
if err != nil {
|
||||
t.Fatal("could not read Go version:", err)
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(testCase, func(t *testing.T) {
|
||||
// Determine which tests to run, depending on the Go and LLVM versions.
|
||||
tests := []testCase{
|
||||
{"basic.go", "", ""},
|
||||
{"pointer.go", "", ""},
|
||||
{"slice.go", "", ""},
|
||||
{"string.go", "", ""},
|
||||
{"float.go", "", ""},
|
||||
{"interface.go", "", ""},
|
||||
{"func.go", "", ""},
|
||||
{"pragma.go", "", ""},
|
||||
{"goroutine.go", "wasm", "asyncify"},
|
||||
{"goroutine.go", "cortex-m-qemu", "tasks"},
|
||||
{"channel.go", "", ""},
|
||||
{"intrinsics.go", "cortex-m-qemu", ""},
|
||||
{"intrinsics.go", "wasm", ""},
|
||||
{"gc.go", "", ""},
|
||||
}
|
||||
if llvmMajor >= 12 {
|
||||
tests = append(tests, testCase{"intrinsics.go", "cortex-m-qemu", ""})
|
||||
tests = append(tests, testCase{"intrinsics.go", "wasm", ""})
|
||||
}
|
||||
if goMinor >= 17 {
|
||||
tests = append(tests, testCase{"go1.17.go", "", ""})
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
name := tc.file
|
||||
targetString := "wasm"
|
||||
if tc.target != "" {
|
||||
targetString = tc.target
|
||||
name += "-" + tc.target
|
||||
}
|
||||
if tc.scheduler != "" {
|
||||
name += "-" + tc.scheduler
|
||||
}
|
||||
|
||||
t.Run(name, func(t *testing.T) {
|
||||
options := &compileopts.Options{
|
||||
Target: targetString,
|
||||
}
|
||||
target, err := compileopts.LoadTarget(options)
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
}
|
||||
if tc.scheduler != "" {
|
||||
options.Scheduler = tc.scheduler
|
||||
}
|
||||
config := &compileopts.Config{
|
||||
Options: options,
|
||||
Target: target,
|
||||
}
|
||||
compilerConfig := &Config{
|
||||
Triple: config.Triple(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
Scheduler: config.Scheduler(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
DefaultStackSize: config.Target.DefaultStackSize,
|
||||
NeedsStackObjects: config.NeedsStackObjects(),
|
||||
}
|
||||
machine, err := NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
|
||||
// Load entire program AST into memory.
|
||||
lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{
|
||||
lprogram, err := loader.Load(config, []string{"./testdata/" + tc.file}, config.ClangHeaders, types.Config{
|
||||
Sizes: Sizes(machine),
|
||||
})
|
||||
if err != nil {
|
||||
@@ -61,19 +123,25 @@ func TestCompiler(t *testing.T) {
|
||||
}
|
||||
err = lprogram.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse test case %s: %s", testCase, err)
|
||||
t.Fatalf("could not parse test case %s: %s", tc.file, err)
|
||||
}
|
||||
|
||||
// Compile AST to IR.
|
||||
program := lprogram.LoadSSA()
|
||||
pkg := lprogram.MainPkg()
|
||||
mod, errs := CompilePackage(testCase, pkg, machine, compilerConfig, false)
|
||||
mod, errs := CompilePackage(tc.file, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||
if errs != nil {
|
||||
for _, err := range errs {
|
||||
t.Log("error:", err)
|
||||
t.Error(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
err = llvm.VerifyModule(mod, llvm.PrintMessageAction)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
// Optimize IR a little.
|
||||
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
|
||||
defer funcPasses.Dispose()
|
||||
@@ -84,18 +152,25 @@ func TestCompiler(t *testing.T) {
|
||||
}
|
||||
funcPasses.FinalizeFunc()
|
||||
|
||||
outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
|
||||
outFilePrefix := tc.file[:len(tc.file)-3]
|
||||
if tc.target != "" {
|
||||
outFilePrefix += "-" + tc.target
|
||||
}
|
||||
if tc.scheduler != "" {
|
||||
outFilePrefix += "-" + tc.scheduler
|
||||
}
|
||||
outPath := "./testdata/" + outFilePrefix + ".ll"
|
||||
|
||||
// Update test if needed. Do not check the result.
|
||||
if *flagUpdate {
|
||||
err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
|
||||
err := ioutil.WriteFile(outPath, []byte(mod.String()), 0666)
|
||||
if err != nil {
|
||||
t.Error("failed to write updated output file:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
expected, err := ioutil.ReadFile(outfile)
|
||||
expected, err := ioutil.ReadFile(outPath)
|
||||
if err != nil {
|
||||
t.Fatal("failed to read golden file:", err)
|
||||
}
|
||||
@@ -107,8 +182,6 @@ func TestCompiler(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var alignRegexp = regexp.MustCompile(", align [0-9]+$")
|
||||
|
||||
// fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
|
||||
// equal. That means, only relevant lines are compared (excluding comments
|
||||
// etc.).
|
||||
@@ -120,15 +193,6 @@ func fuzzyEqualIR(s1, s2 string) bool {
|
||||
}
|
||||
for i, line1 := range lines1 {
|
||||
line2 := lines2[i]
|
||||
match1 := alignRegexp.MatchString(line1)
|
||||
match2 := alignRegexp.MatchString(line2)
|
||||
if match1 != match2 {
|
||||
// Only one of the lines has the align keyword. Remove it.
|
||||
// This is a change to make the test work in both LLVM 10 and LLVM
|
||||
// 11 (LLVM 11 appears to automatically add alignment everywhere).
|
||||
line1 = alignRegexp.ReplaceAllString(line1, "")
|
||||
line2 = alignRegexp.ReplaceAllString(line2, "")
|
||||
}
|
||||
if line1 != line2 {
|
||||
return false
|
||||
}
|
||||
@@ -157,13 +221,14 @@ func filterIrrelevantIRLines(lines []string) []string {
|
||||
if strings.HasPrefix(line, "source_filename = ") {
|
||||
continue
|
||||
}
|
||||
if llvmVersion < 10 && strings.HasPrefix(line, "attributes ") {
|
||||
if llvmVersion < 12 && strings.HasPrefix(line, "attributes ") {
|
||||
// Ignore attribute groups. These may change between LLVM versions.
|
||||
// Right now test outputs are for LLVM 10.
|
||||
// Right now test outputs are for LLVM 12 and higher.
|
||||
continue
|
||||
}
|
||||
if llvmVersion < 10 && strings.HasPrefix(line, "target datalayout ") {
|
||||
// Ignore the target layout. This may change between LLVM versions.
|
||||
if llvmVersion < 13 && strings.HasPrefix(line, "target datalayout = ") {
|
||||
// The datalayout string may vary betewen LLVM versions.
|
||||
// Right now test outputs are for LLVM 13 and higher.
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
|
||||
+8
-16
@@ -217,7 +217,8 @@ func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
// This may be hit a variable number of times, so use a heap allocation.
|
||||
size := b.targetData.TypeAllocSize(deferFrameType)
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
|
||||
nilPtr := llvm.ConstNull(b.i8ptrType)
|
||||
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue, nilPtr}, "defer.alloc.call")
|
||||
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
|
||||
}
|
||||
if b.NeedsStackObjects {
|
||||
@@ -331,10 +332,10 @@ func (b *builder) createRunDefers() {
|
||||
//Pass context
|
||||
forwardParams = append(forwardParams, context)
|
||||
} else {
|
||||
// Isolate the typecode.
|
||||
typecode := forwardParams[0]
|
||||
forwardParams = forwardParams[1:]
|
||||
fnPtr = b.getInvokePtr(callback, typecode)
|
||||
// Move typecode from the start to the end of the list of
|
||||
// parameters.
|
||||
forwardParams = append(forwardParams[1:], forwardParams[0])
|
||||
fnPtr = b.getInvokeFunction(callback)
|
||||
|
||||
// Add the context parameter. An interface call cannot also be a
|
||||
// closure but we have to supply the parameter anyway for platforms
|
||||
@@ -342,9 +343,6 @@ func (b *builder) createRunDefers() {
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
}
|
||||
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
|
||||
b.createCall(fnPtr, forwardParams, "")
|
||||
|
||||
case *ssa.Function:
|
||||
@@ -352,7 +350,7 @@ func (b *builder) createRunDefers() {
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||
for _, param := range callback.Params {
|
||||
for _, param := range getParams(callback.Signature) {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
|
||||
}
|
||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||
@@ -361,7 +359,7 @@ func (b *builder) createRunDefers() {
|
||||
// Extract the params from the struct.
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := range callback.Params {
|
||||
for i := range getParams(callback.Signature) {
|
||||
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
forwardParam := b.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
@@ -373,9 +371,6 @@ func (b *builder) createRunDefers() {
|
||||
// Add the context parameter. We know it is ignored by the receiving
|
||||
// function, but we have to pass one anyway.
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
}
|
||||
|
||||
// Call real function.
|
||||
@@ -402,9 +397,6 @@ func (b *builder) createRunDefers() {
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
|
||||
// Call deferred function.
|
||||
b.createCall(b.getFunction(fn), forwardParams, "")
|
||||
case *ssa.Builtin:
|
||||
|
||||
+1
-11
@@ -3,7 +3,6 @@ package compiler
|
||||
// This file contains some utility functions related to error handling.
|
||||
|
||||
import (
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"path/filepath"
|
||||
@@ -20,20 +19,11 @@ func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
|
||||
}
|
||||
}
|
||||
|
||||
// addError adds a new compiler diagnostic with the given location and message.
|
||||
func (c *compilerContext) addError(pos token.Pos, msg string) {
|
||||
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
||||
}
|
||||
|
||||
// errorAt returns an error value at the location of the instruction.
|
||||
// The location information may not be complete as it depends on debug
|
||||
// information in the IR.
|
||||
func errorAt(inst llvm.Value, msg string) scanner.Error {
|
||||
return scanner.Error{
|
||||
Pos: getPosition(inst),
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
// getPosition returns the position information for the given value, as far as
|
||||
// it is available.
|
||||
func getPosition(val llvm.Value) token.Position {
|
||||
|
||||
+24
-46
@@ -19,30 +19,8 @@ func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signat
|
||||
// createFuncValue creates a function value from a raw function pointer with no
|
||||
// context.
|
||||
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
||||
var funcValueScalar llvm.Value
|
||||
switch c.FuncImplementation {
|
||||
case "doubleword":
|
||||
// Closure is: {context, function pointer}
|
||||
funcValueScalar = funcPtr
|
||||
case "switch":
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||
if funcValueWithSignatureGlobal.IsNil() {
|
||||
funcValueWithSignatureType := c.getLLVMRuntimeType("funcValueWithSignature")
|
||||
funcValueWithSignature := llvm.ConstNamedStruct(funcValueWithSignatureType, []llvm.Value{
|
||||
llvm.ConstPtrToInt(funcPtr, c.uintptrType),
|
||||
sigGlobal,
|
||||
})
|
||||
funcValueWithSignatureGlobal = llvm.AddGlobal(c.mod, funcValueWithSignatureType, funcValueWithSignatureGlobalName)
|
||||
funcValueWithSignatureGlobal.SetInitializer(funcValueWithSignature)
|
||||
funcValueWithSignatureGlobal.SetGlobalConstant(true)
|
||||
funcValueWithSignatureGlobal.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
funcValueScalar = llvm.ConstPtrToInt(funcValueWithSignatureGlobal, c.uintptrType)
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
// Closure is: {context, function pointer}
|
||||
funcValueScalar := llvm.ConstBitCast(funcPtr, c.rawVoidFuncType)
|
||||
funcValueType := c.getFuncType(sig)
|
||||
funcValue := llvm.Undef(funcValueType)
|
||||
funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
|
||||
@@ -50,6 +28,19 @@ func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context
|
||||
return funcValue
|
||||
}
|
||||
|
||||
// getFuncSignatureID returns a new external global for a given signature. This
|
||||
// global reference is not real, it is only used during func lowering to assign
|
||||
// signature types to functions and will then be removed.
|
||||
func (c *compilerContext) getFuncSignatureID(sig *types.Signature) llvm.Value {
|
||||
sigGlobalName := "reflect/types.funcid:" + getTypeCodeName(sig)
|
||||
sigGlobal := c.mod.NamedGlobal(sigGlobalName)
|
||||
if sigGlobal.IsNil() {
|
||||
sigGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), sigGlobalName)
|
||||
sigGlobal.SetGlobalConstant(true)
|
||||
}
|
||||
return sigGlobal
|
||||
}
|
||||
|
||||
// extractFuncScalar returns some scalar that can be used in comparisons. It is
|
||||
// a cheap operation.
|
||||
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
||||
@@ -66,31 +57,19 @@ func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
||||
// value. This may be an expensive operation.
|
||||
func (b *builder) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
|
||||
context = b.CreateExtractValue(funcValue, 0, "")
|
||||
switch b.FuncImplementation {
|
||||
case "doubleword":
|
||||
funcPtr = b.CreateExtractValue(funcValue, 1, "")
|
||||
case "switch":
|
||||
llvmSig := b.getRawFuncType(sig)
|
||||
sigGlobal := b.getTypeCode(sig)
|
||||
funcPtr = b.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||
funcPtr = b.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
bitcast := b.CreateExtractValue(funcValue, 1, "")
|
||||
if !bitcast.IsAConstantExpr().IsNil() && bitcast.Opcode() == llvm.BitCast {
|
||||
funcPtr = bitcast.Operand(0)
|
||||
return
|
||||
}
|
||||
llvmSig := b.getRawFuncType(sig)
|
||||
funcPtr = b.CreateBitCast(bitcast, llvmSig, "")
|
||||
return
|
||||
}
|
||||
|
||||
// getFuncType returns the type of a func value given a signature.
|
||||
func (c *compilerContext) getFuncType(typ *types.Signature) llvm.Type {
|
||||
switch c.FuncImplementation {
|
||||
case "doubleword":
|
||||
rawPtr := c.getRawFuncType(typ)
|
||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, rawPtr}, false)
|
||||
case "switch":
|
||||
return c.getLLVMRuntimeType("funcValue")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
}
|
||||
return c.ctx.StructType([]llvm.Type{c.i8ptrType, c.rawVoidFuncType}, false)
|
||||
}
|
||||
|
||||
// getRawFuncType returns a LLVM function pointer type for a given signature.
|
||||
@@ -124,19 +103,18 @@ func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
|
||||
// The receiver is not an interface, but a i8* type.
|
||||
recv = c.i8ptrType
|
||||
}
|
||||
for _, info := range expandFormalParamType(recv, "", nil) {
|
||||
for _, info := range c.expandFormalParamType(recv, "", nil) {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
}
|
||||
for i := 0; i < typ.Params().Len(); i++ {
|
||||
subType := c.getLLVMType(typ.Params().At(i).Type())
|
||||
for _, info := range expandFormalParamType(subType, "", nil) {
|
||||
for _, info := range c.expandFormalParamType(subType, "", nil) {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
}
|
||||
// All functions take these parameters at the end.
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // context
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // parent coroutine
|
||||
|
||||
// Make a func type out of the signature.
|
||||
return llvm.PointerType(llvm.FunctionType(returnType, paramTypes, false), c.funcPtrAddrSpace)
|
||||
|
||||
+145
-46
@@ -5,48 +5,116 @@ package compiler
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// createGoInstruction starts a new goroutine with the provided function pointer
|
||||
// and parameters.
|
||||
// In general, you should pass all regular parameters plus the context parameter.
|
||||
// There is one exception: the task-based scheduler needs to have the function
|
||||
// pointer passed in as a parameter too in addition to the context.
|
||||
//
|
||||
// Because a go statement doesn't return anything, return undef.
|
||||
func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, prefix string, pos token.Pos) llvm.Value {
|
||||
paramBundle := b.emitPointerPack(params)
|
||||
var callee, stackSize llvm.Value
|
||||
switch b.Scheduler {
|
||||
case "none", "tasks":
|
||||
callee = b.createGoroutineStartWrapper(funcPtr, prefix, pos)
|
||||
if b.AutomaticStackSize {
|
||||
// The stack size is not known until after linking. Call a dummy
|
||||
// function that will be replaced with a load from a special ELF
|
||||
// section that contains the stack size (and is modified after
|
||||
// linking).
|
||||
stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
|
||||
stackSize = b.createCall(stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType), llvm.Undef(b.i8ptrType)}, "stacksize")
|
||||
} else {
|
||||
// The stack size is fixed at compile time. By emitting it here as a
|
||||
// constant, it can be optimized.
|
||||
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
|
||||
// createGo emits code to start a new goroutine.
|
||||
func (b *builder) createGo(instr *ssa.Go) {
|
||||
// Get all function parameters to pass to the goroutine.
|
||||
var params []llvm.Value
|
||||
for _, param := range instr.Call.Args {
|
||||
params = append(params, b.getValue(param))
|
||||
}
|
||||
|
||||
var prefix string
|
||||
var funcPtr llvm.Value
|
||||
hasContext := false
|
||||
if callee := instr.Call.StaticCallee(); callee != nil {
|
||||
// Static callee is known. This makes it easier to start a new
|
||||
// goroutine.
|
||||
var context llvm.Value
|
||||
switch value := instr.Call.Value.(type) {
|
||||
case *ssa.Function:
|
||||
// Goroutine call is regular function call. No context is necessary.
|
||||
case *ssa.MakeClosure:
|
||||
// A goroutine call on a func value, but the callee is trivial to find. For
|
||||
// example: immediately applied functions.
|
||||
funcValue := b.getValue(value)
|
||||
context = b.extractFuncContext(funcValue)
|
||||
default:
|
||||
panic("StaticCallee returned an unexpected value")
|
||||
}
|
||||
case "coroutines":
|
||||
callee = b.CreatePtrToInt(funcPtr, b.uintptrType, "")
|
||||
// There is no goroutine stack size: coroutines are used instead of
|
||||
// stacks.
|
||||
stackSize = llvm.Undef(b.uintptrType)
|
||||
default:
|
||||
panic("unreachable")
|
||||
if !context.IsNil() {
|
||||
params = append(params, context) // context parameter
|
||||
hasContext = true
|
||||
}
|
||||
funcPtr = b.getFunction(callee)
|
||||
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
||||
// We cheat. None of the builtins do any long or blocking operation, so
|
||||
// we might as well run these builtins right away without the program
|
||||
// noticing the difference.
|
||||
// Possible exceptions:
|
||||
// - copy: this is a possibly long operation, but not a blocking
|
||||
// operation. Semantically it makes no difference to run it right
|
||||
// away (not in a goroutine). However, in practice it makes no sense
|
||||
// to run copy in a goroutine as there is no way to (safely) know
|
||||
// when it is finished.
|
||||
// - panic: the error message would appear in the parent goroutine.
|
||||
// But because `go panic("err")` would halt the program anyway
|
||||
// (there is no recover), panicking right away would give the same
|
||||
// behavior as creating a goroutine, switching the scheduler to that
|
||||
// goroutine, and panicking there. So this optimization seems
|
||||
// correct.
|
||||
// - recover: because it runs in a new goroutine, it is never a
|
||||
// deferred function. Thus this is a no-op.
|
||||
if builtin.Name() == "recover" {
|
||||
// This is a no-op, even in a deferred function:
|
||||
// go recover()
|
||||
return
|
||||
}
|
||||
var argTypes []types.Type
|
||||
var argValues []llvm.Value
|
||||
for _, arg := range instr.Call.Args {
|
||||
argTypes = append(argTypes, arg.Type())
|
||||
argValues = append(argValues, b.getValue(arg))
|
||||
}
|
||||
b.createBuiltin(argTypes, argValues, builtin.Name(), instr.Pos())
|
||||
return
|
||||
} else if instr.Call.IsInvoke() {
|
||||
// This is a method call on an interface value.
|
||||
itf := b.getValue(instr.Call.Value)
|
||||
itfTypeCode := b.CreateExtractValue(itf, 0, "")
|
||||
itfValue := b.CreateExtractValue(itf, 1, "")
|
||||
funcPtr = b.getInvokeFunction(&instr.Call)
|
||||
params = append([]llvm.Value{itfValue}, params...) // start with receiver
|
||||
params = append(params, itfTypeCode) // end with typecode
|
||||
} else {
|
||||
// This is a function pointer.
|
||||
// At the moment, two extra params are passed to the newly started
|
||||
// goroutine:
|
||||
// * The function context, for closures.
|
||||
// * The function pointer (for tasks).
|
||||
var context llvm.Value
|
||||
funcPtr, context = b.decodeFuncValue(b.getValue(instr.Call.Value), instr.Call.Value.Type().Underlying().(*types.Signature))
|
||||
params = append(params, context, funcPtr)
|
||||
hasContext = true
|
||||
prefix = b.fn.RelString(nil)
|
||||
}
|
||||
|
||||
paramBundle := b.emitPointerPack(params)
|
||||
var stackSize llvm.Value
|
||||
callee := b.createGoroutineStartWrapper(funcPtr, prefix, hasContext, instr.Pos())
|
||||
if b.AutomaticStackSize {
|
||||
// The stack size is not known until after linking. Call a dummy
|
||||
// function that will be replaced with a load from a special ELF
|
||||
// section that contains the stack size (and is modified after
|
||||
// linking).
|
||||
stackSizeFn := b.getFunction(b.program.ImportedPackage("internal/task").Members["getGoroutineStackSize"].(*ssa.Function))
|
||||
stackSize = b.createCall(stackSizeFn, []llvm.Value{callee, llvm.Undef(b.i8ptrType)}, "stacksize")
|
||||
} else {
|
||||
// The stack size is fixed at compile time. By emitting it here as a
|
||||
// constant, it can be optimized.
|
||||
if (b.Scheduler == "tasks" || b.Scheduler == "asyncify") && b.DefaultStackSize == 0 {
|
||||
b.addError(instr.Pos(), "default stack size for goroutines is not set")
|
||||
}
|
||||
stackSize = llvm.ConstInt(b.uintptrType, b.DefaultStackSize, false)
|
||||
}
|
||||
start := b.getFunction(b.program.ImportedPackage("internal/task").Members["start"].(*ssa.Function))
|
||||
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType), llvm.ConstPointerNull(b.i8ptrType)}, "")
|
||||
return llvm.Undef(funcPtr.Type().ElementType().ReturnType())
|
||||
b.createCall(start, []llvm.Value{callee, paramBundle, stackSize, llvm.Undef(b.i8ptrType)}, "")
|
||||
}
|
||||
|
||||
// createGoroutineStartWrapper creates a wrapper for the task-based
|
||||
@@ -67,12 +135,22 @@ func (b *builder) createGoInstruction(funcPtr llvm.Value, params []llvm.Value, p
|
||||
// allows a single (pointer) argument to the newly started goroutine. Also, it
|
||||
// ignores the return value because newly started goroutines do not have a
|
||||
// return value.
|
||||
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
|
||||
//
|
||||
// The hasContext parameter indicates whether the context parameter (the second
|
||||
// to last parameter of the function) is used for this wrapper. If hasContext is
|
||||
// false, the parameter bundle is assumed to have no context parameter and undef
|
||||
// is passed instead.
|
||||
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, hasContext bool, pos token.Pos) llvm.Value {
|
||||
var wrapper llvm.Value
|
||||
|
||||
builder := c.ctx.NewBuilder()
|
||||
defer builder.Dispose()
|
||||
|
||||
var deadlock llvm.Value
|
||||
if c.Scheduler == "asyncify" {
|
||||
deadlock = c.getFunction(c.program.ImportedPackage("runtime").Members["deadlock"].(*ssa.Function))
|
||||
}
|
||||
|
||||
if !fn.IsAFunction().IsNil() {
|
||||
// See whether this wrapper has already been created. If so, return it.
|
||||
name := fn.Name()
|
||||
@@ -84,7 +162,8 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
|
||||
// Create the wrapper.
|
||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
||||
wrapper = llvm.AddFunction(c.mod, name+"$gowrapper", wrapperType)
|
||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||
c.addStandardAttributes(wrapper)
|
||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
|
||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
@@ -114,12 +193,23 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
|
||||
|
||||
// Create the list of params for the call.
|
||||
paramTypes := fn.Type().ElementType().ParamTypes()
|
||||
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
|
||||
params = append(params, llvm.Undef(c.i8ptrType))
|
||||
if !hasContext {
|
||||
paramTypes = paramTypes[:len(paramTypes)-1] // strip context parameter
|
||||
}
|
||||
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
|
||||
if !hasContext {
|
||||
params = append(params, llvm.Undef(c.i8ptrType)) // add dummy context parameter
|
||||
}
|
||||
|
||||
// Create the call.
|
||||
builder.CreateCall(fn, params, "")
|
||||
|
||||
if c.Scheduler == "asyncify" {
|
||||
builder.CreateCall(deadlock, []llvm.Value{
|
||||
llvm.Undef(c.i8ptrType),
|
||||
}, "")
|
||||
}
|
||||
|
||||
} else {
|
||||
// For a function pointer like this:
|
||||
//
|
||||
@@ -141,7 +231,8 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
|
||||
// Create the wrapper.
|
||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
||||
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
|
||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||
c.addStandardAttributes(wrapper)
|
||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
|
||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
@@ -171,23 +262,31 @@ func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix stri
|
||||
|
||||
// Get the list of parameters, with the extra parameters at the end.
|
||||
paramTypes := fn.Type().ElementType().ParamTypes()
|
||||
paramTypes[len(paramTypes)-1] = fn.Type() // the last element is the function pointer
|
||||
paramTypes = append(paramTypes, fn.Type()) // the last element is the function pointer
|
||||
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
|
||||
|
||||
// Get the function pointer.
|
||||
fnPtr := params[len(params)-1]
|
||||
|
||||
// Ignore the last param, which isn't used anymore.
|
||||
// TODO: avoid this extra "parent handle" parameter in most functions.
|
||||
params[len(params)-1] = llvm.Undef(c.i8ptrType)
|
||||
params = params[:len(params)-1]
|
||||
|
||||
// Create the call.
|
||||
builder.CreateCall(fnPtr, params, "")
|
||||
|
||||
if c.Scheduler == "asyncify" {
|
||||
builder.CreateCall(deadlock, []llvm.Value{
|
||||
llvm.Undef(c.i8ptrType),
|
||||
}, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Finish the function. Every basic block must end in a terminator, and
|
||||
// because goroutines never return a value we can simply return void.
|
||||
builder.CreateRetVoid()
|
||||
if c.Scheduler == "asyncify" {
|
||||
// The goroutine was terminated via deadlock.
|
||||
builder.CreateUnreachable()
|
||||
} else {
|
||||
// Finish the function. Every basic block must end in a terminator, and
|
||||
// because goroutines never return a value we can simply return void.
|
||||
builder.CreateRetVoid()
|
||||
}
|
||||
|
||||
// Return a ptrtoint of the wrapper, not the function itself.
|
||||
return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
|
||||
|
||||
+10
-10
@@ -24,7 +24,7 @@ func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
|
||||
// Magic function: insert inline assembly instead of calling it.
|
||||
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{}, false)
|
||||
asm := constant.StringVal(args[0].(*ssa.Const).Value)
|
||||
target := llvm.InlineAsm(fnType, asm, "", true, false, 0)
|
||||
target := llvm.InlineAsm(fnType, asm, "", true, false, 0, false)
|
||||
return b.CreateCall(target, nil, ""), nil
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
|
||||
args := []llvm.Value{}
|
||||
constraints := []string{}
|
||||
hasOutput := false
|
||||
asmString = regexp.MustCompile("\\{\\}").ReplaceAllStringFunc(asmString, func(s string) string {
|
||||
asmString = regexp.MustCompile(`\{\}`).ReplaceAllStringFunc(asmString, func(s string) string {
|
||||
hasOutput = true
|
||||
return "$0"
|
||||
})
|
||||
@@ -80,7 +80,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
|
||||
constraints = append(constraints, "=&r")
|
||||
registerNumbers[""] = 0
|
||||
}
|
||||
asmString = regexp.MustCompile("\\{[a-zA-Z]+\\}").ReplaceAllStringFunc(asmString, func(s string) string {
|
||||
asmString = regexp.MustCompile(`\{[a-zA-Z]+\}`).ReplaceAllStringFunc(asmString, func(s string) string {
|
||||
// TODO: skip strings like {r4} etc. that look like ARM push/pop
|
||||
// instructions.
|
||||
name := s[1 : len(s)-1]
|
||||
@@ -116,7 +116,7 @@ func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error)
|
||||
outputType = b.ctx.VoidType()
|
||||
}
|
||||
fnType := llvm.FunctionType(outputType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
|
||||
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0, false)
|
||||
result := b.CreateCall(target, args, "")
|
||||
if hasOutput {
|
||||
return result, nil
|
||||
@@ -159,7 +159,7 @@ func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
|
||||
// marked as clobbered.
|
||||
constraints += ",~{r1},~{r2},~{r3}"
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
|
||||
return b.CreateCall(target, llvmArgs, ""), nil
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ func (b *builder) emitSV64Call(args []ssa.Value) (llvm.Value, error) {
|
||||
// marked as clobbered.
|
||||
constraints += ",~{x1},~{x2},~{x3},~{x4},~{x5},~{x6},~{x7}"
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0, false)
|
||||
return b.CreateCall(target, llvmArgs, ""), nil
|
||||
}
|
||||
|
||||
@@ -222,24 +222,24 @@ func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
// marked as such.
|
||||
fnType := llvm.FunctionType(b.uintptrType, nil, false)
|
||||
asm := fmt.Sprintf("csrr $0, %d", csr)
|
||||
target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0)
|
||||
target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0, false)
|
||||
return b.CreateCall(target, nil, ""), nil
|
||||
case "Set":
|
||||
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.uintptrType}, false)
|
||||
asm := fmt.Sprintf("csrw %d, $0", csr)
|
||||
target := llvm.InlineAsm(fnType, asm, "r", true, false, 0)
|
||||
target := llvm.InlineAsm(fnType, asm, "r", true, false, 0, false)
|
||||
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
|
||||
case "SetBits":
|
||||
// Note: it may be possible to optimize this to csrrsi in many cases.
|
||||
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
|
||||
asm := fmt.Sprintf("csrrs $0, %d, $1", csr)
|
||||
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
|
||||
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0, false)
|
||||
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
|
||||
case "ClearBits":
|
||||
// Note: it may be possible to optimize this to csrrci in many cases.
|
||||
fnType := llvm.FunctionType(b.uintptrType, []llvm.Type{b.uintptrType}, false)
|
||||
asm := fmt.Sprintf("csrrc $0, %d, $1", csr)
|
||||
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0)
|
||||
target := llvm.InlineAsm(fnType, asm, "=r,r", true, false, 0, false)
|
||||
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
|
||||
|
||||
+132
-68
@@ -24,22 +24,22 @@ import (
|
||||
func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
|
||||
itfValue := b.emitPointerPack([]llvm.Value{val})
|
||||
itfTypeCodeGlobal := b.getTypeCode(typ)
|
||||
itfMethodSetGlobal := b.getTypeMethodSet(typ)
|
||||
itfConcreteTypeGlobal := b.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||
if itfConcreteTypeGlobal.IsNil() {
|
||||
typeInInterface := b.getLLVMRuntimeType("typeInInterface")
|
||||
itfConcreteTypeGlobal = llvm.AddGlobal(b.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
|
||||
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
|
||||
itfConcreteTypeGlobal.SetGlobalConstant(true)
|
||||
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||
}
|
||||
itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "")
|
||||
itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "")
|
||||
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
|
||||
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||
itf = b.CreateInsertValue(itf, itfValue, 1, "")
|
||||
return itf
|
||||
}
|
||||
|
||||
// extractValueFromInterface extract the value from an interface value
|
||||
// (runtime._interface) under the assumption that it is of the type given in
|
||||
// llvmType. The behavior is undefied if the interface is nil or llvmType
|
||||
// doesn't match the underlying type of the interface.
|
||||
func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value {
|
||||
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||
return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0]
|
||||
}
|
||||
|
||||
// getTypeCode returns a reference to a type code.
|
||||
// It returns a pointer to an external global which should be replaced with the
|
||||
// real type in the interface lowering pass.
|
||||
@@ -54,6 +54,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
// reflect lowering simpler.
|
||||
var references llvm.Value
|
||||
var length int64
|
||||
var methodSet llvm.Value
|
||||
var ptrTo llvm.Value
|
||||
var typeAssert llvm.Value
|
||||
switch typ := typ.(type) {
|
||||
case *types.Named:
|
||||
references = c.getTypeCode(typ.Underlying())
|
||||
@@ -70,18 +73,38 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
// Take a pointer to the typecodeID of the first field (if it exists).
|
||||
structGlobal := c.makeStructTypeFields(typ)
|
||||
references = llvm.ConstBitCast(structGlobal, global.Type())
|
||||
case *types.Interface:
|
||||
methodSetGlobal := c.getInterfaceMethodSet(typ)
|
||||
references = llvm.ConstBitCast(methodSetGlobal, global.Type())
|
||||
}
|
||||
if _, ok := typ.Underlying().(*types.Interface); !ok {
|
||||
methodSet = c.getTypeMethodSet(typ)
|
||||
} else {
|
||||
typeAssert = c.getInterfaceImplementsFunc(typ)
|
||||
typeAssert = llvm.ConstPtrToInt(typeAssert, c.uintptrType)
|
||||
}
|
||||
if _, ok := typ.Underlying().(*types.Pointer); !ok {
|
||||
ptrTo = c.getTypeCode(types.NewPointer(typ))
|
||||
}
|
||||
globalValue := llvm.ConstNull(global.Type().ElementType())
|
||||
if !references.IsNil() {
|
||||
// Set the 'references' field of the runtime.typecodeID struct.
|
||||
globalValue := llvm.ConstNull(global.Type().ElementType())
|
||||
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
|
||||
if length != 0 {
|
||||
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
|
||||
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
|
||||
}
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
}
|
||||
if length != 0 {
|
||||
lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false)
|
||||
globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1})
|
||||
}
|
||||
if !methodSet.IsNil() {
|
||||
globalValue = llvm.ConstInsertValue(globalValue, methodSet, []uint32{2})
|
||||
}
|
||||
if !ptrTo.IsNil() {
|
||||
globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3})
|
||||
}
|
||||
if !typeAssert.IsNil() {
|
||||
globalValue = llvm.ConstInsertValue(globalValue, typeAssert, []uint32{4})
|
||||
}
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
}
|
||||
return global
|
||||
@@ -103,8 +126,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
||||
fieldName.SetLinkage(llvm.PrivateLinkage)
|
||||
fieldName.SetUnnamedAddr(true)
|
||||
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
})
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
|
||||
if typ.Tag(i) != "" {
|
||||
@@ -112,8 +135,8 @@ func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
||||
fieldTag.SetLinkage(llvm.PrivateLinkage)
|
||||
fieldTag.SetUnnamedAddr(true)
|
||||
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
llvm.ConstInt(llvm.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
})
|
||||
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
|
||||
}
|
||||
@@ -186,7 +209,11 @@ func getTypeCodeName(t types.Type) string {
|
||||
case *types.Interface:
|
||||
methods := make([]string, t.NumMethods())
|
||||
for i := 0; i < t.NumMethods(); i++ {
|
||||
methods[i] = getTypeCodeName(t.Method(i).Type())
|
||||
name := t.Method(i).Name()
|
||||
if !token.IsExported(name) {
|
||||
name = t.Method(i).Pkg().Path() + "." + name
|
||||
}
|
||||
methods[i] = name + ":" + getTypeCodeName(t.Method(i).Type())
|
||||
}
|
||||
return "interface:" + "{" + strings.Join(methods, ",") + "}"
|
||||
case *types.Map:
|
||||
@@ -264,7 +291,7 @@ func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
|
||||
global.SetInitializer(value)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
@@ -295,19 +322,37 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
|
||||
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
|
||||
global.SetInitializer(value)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
// getMethodSignatureName returns a unique name (that can be used as the name of
|
||||
// a global) for the given method.
|
||||
func (c *compilerContext) getMethodSignatureName(method *types.Func) string {
|
||||
signature := methodSignature(method)
|
||||
var globalName string
|
||||
if token.IsExported(method.Name()) {
|
||||
globalName = "reflect/methods." + signature
|
||||
} else {
|
||||
globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
|
||||
}
|
||||
return globalName
|
||||
}
|
||||
|
||||
// getMethodSignature returns a global variable which is a reference to an
|
||||
// external *i8 indicating the indicating the signature of this method. It is
|
||||
// used during the interface lowering pass.
|
||||
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
|
||||
signature := methodSignature(method)
|
||||
signatureGlobal := c.mod.NamedGlobal("func " + signature)
|
||||
globalName := c.getMethodSignatureName(method)
|
||||
signatureGlobal := c.mod.NamedGlobal(globalName)
|
||||
if signatureGlobal.IsNil() {
|
||||
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
|
||||
// TODO: put something useful in these globals, such as the method
|
||||
// signature. Useful to one day implement reflect.Value.Method(n).
|
||||
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName)
|
||||
signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false))
|
||||
signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
signatureGlobal.SetGlobalConstant(true)
|
||||
signatureGlobal.SetAlignment(1)
|
||||
}
|
||||
return signatureGlobal
|
||||
}
|
||||
@@ -327,21 +372,28 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
commaOk := llvm.Value{}
|
||||
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||
// Type assert on interface type.
|
||||
// This pseudo call will be lowered in the interface lowering pass to a
|
||||
// real call which checks whether the provided typecode is any of the
|
||||
// concrete types that implements this interface.
|
||||
// This is a call to an interface type assert function.
|
||||
// The interface lowering pass will define this function by filling it
|
||||
// with a type switch over all concrete types that implement this
|
||||
// interface, and returning whether it's one of the matched types.
|
||||
// This is very different from how interface asserts are implemented in
|
||||
// the main Go compiler, where the runtime checks whether the type
|
||||
// implements each method of the interface. See:
|
||||
// https://research.swtch.com/interfaces
|
||||
methodSet := b.getInterfaceMethodSet(expr.AssertedType)
|
||||
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
|
||||
fn := b.getInterfaceImplementsFunc(expr.AssertedType)
|
||||
commaOk = b.CreateCall(fn, []llvm.Value{actualTypeNum}, "")
|
||||
|
||||
} else {
|
||||
globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType)
|
||||
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
|
||||
if assertedTypeCodeGlobal.IsNil() {
|
||||
// Create a new typecode global.
|
||||
assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName)
|
||||
assertedTypeCodeGlobal.SetGlobalConstant(true)
|
||||
}
|
||||
// Type assert on concrete type.
|
||||
// Call runtime.typeAssert, which will be lowered to a simple icmp or
|
||||
// const false in the interface lowering pass.
|
||||
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
|
||||
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
||||
}
|
||||
|
||||
@@ -373,8 +425,7 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
} else {
|
||||
// Type assert on concrete type. Extract the underlying type from
|
||||
// the interface (but only after checking it matches).
|
||||
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||
valueOk = b.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
|
||||
valueOk = b.extractValueFromInterface(itf, assertedType)
|
||||
}
|
||||
b.CreateBr(nextBlock)
|
||||
|
||||
@@ -396,39 +447,52 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
}
|
||||
}
|
||||
|
||||
// getInvokePtr creates an interface function pointer lookup for the specified invoke instruction, using a specified typecode.
|
||||
func (b *builder) getInvokePtr(instr *ssa.CallCommon, typecode llvm.Value) llvm.Value {
|
||||
llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature))
|
||||
values := []llvm.Value{
|
||||
typecode,
|
||||
b.getInterfaceMethodSet(instr.Value.Type()),
|
||||
b.getMethodSignature(instr.Method),
|
||||
// getMethodsString returns a string to be used in the "tinygo-methods" string
|
||||
// attribute for interface functions.
|
||||
func (c *compilerContext) getMethodsString(itf *types.Interface) string {
|
||||
methods := make([]string, itf.NumMethods())
|
||||
for i := range methods {
|
||||
methods[i] = c.getMethodSignatureName(itf.Method(i))
|
||||
}
|
||||
fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func")
|
||||
return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
|
||||
return strings.Join(methods, "; ")
|
||||
}
|
||||
|
||||
// getInvokeCall creates and returns the function pointer and parameters of an
|
||||
// interface call.
|
||||
func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
|
||||
// Call an interface method with dynamic dispatch.
|
||||
itf := b.getValue(instr.Value) // interface
|
||||
|
||||
typecode := b.CreateExtractValue(itf, 0, "invoke.typecode")
|
||||
fnCast := b.getInvokePtr(instr, typecode)
|
||||
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||
|
||||
args := []llvm.Value{receiverValue}
|
||||
for _, arg := range instr.Args {
|
||||
args = append(args, b.getValue(arg))
|
||||
// getInterfaceImplementsfunc returns a declared function that works as a type
|
||||
// switch. The interface lowering pass will define this function.
|
||||
func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value {
|
||||
fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert"
|
||||
llvmFn := c.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false)
|
||||
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
|
||||
c.addStandardDeclaredAttributes(llvmFn)
|
||||
methods := c.getMethodsString(assertedType.Underlying().(*types.Interface))
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
|
||||
}
|
||||
// Add the context parameter. An interface call never takes a context but we
|
||||
// have to supply the parameter anyway.
|
||||
args = append(args, llvm.Undef(b.i8ptrType))
|
||||
// Add the parent goroutine handle.
|
||||
args = append(args, llvm.Undef(b.i8ptrType))
|
||||
return llvmFn
|
||||
}
|
||||
|
||||
return fnCast, args
|
||||
// getInvokeFunction returns the thunk to call the given interface method. The
|
||||
// thunk is declared, not defined: it will be defined by the interface lowering
|
||||
// pass.
|
||||
func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value {
|
||||
fnName := getTypeCodeName(instr.Value.Type().Underlying()) + "." + instr.Method.Name() + "$invoke"
|
||||
llvmFn := c.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
sig := instr.Method.Type().(*types.Signature)
|
||||
var paramTuple []*types.Var
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
paramTuple = append(paramTuple, sig.Params().At(i))
|
||||
}
|
||||
paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.Uintptr]))
|
||||
llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)).ElementType()
|
||||
llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType)
|
||||
c.addStandardDeclaredAttributes(llvmFn)
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method)))
|
||||
methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface))
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
|
||||
}
|
||||
return llvmFn
|
||||
}
|
||||
|
||||
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be
|
||||
@@ -445,9 +509,9 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
|
||||
}
|
||||
|
||||
// Get the expanded receiver type.
|
||||
receiverType := c.getLLVMType(fn.Params[0].Type())
|
||||
receiverType := c.getLLVMType(fn.Signature.Recv().Type())
|
||||
var expandedReceiverType []llvm.Type
|
||||
for _, info := range expandFormalParamType(receiverType, "", nil) {
|
||||
for _, info := range c.expandFormalParamType(receiverType, "", nil) {
|
||||
expandedReceiverType = append(expandedReceiverType, info.llvmType)
|
||||
}
|
||||
|
||||
@@ -465,9 +529,9 @@ func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llv
|
||||
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
|
||||
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
|
||||
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
|
||||
wrapper.LastParam().SetName("parentHandle")
|
||||
c.addStandardAttributes(wrapper)
|
||||
|
||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||
wrapper.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
|
||||
// Create a new builder just to create this wrapper.
|
||||
|
||||
@@ -36,22 +36,22 @@ func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, erro
|
||||
// Fall back to a generic error.
|
||||
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
|
||||
}
|
||||
funcRawPtr, funcContext := b.decodeFuncValue(funcValue, nil)
|
||||
funcPtr := llvm.ConstPtrToInt(funcRawPtr, b.uintptrType)
|
||||
|
||||
// Create a new global of type runtime/interrupt.handle. Globals of this
|
||||
// type are lowered in the interrupt lowering pass.
|
||||
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
||||
globalLLVMType := b.getLLVMType(globalType)
|
||||
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
||||
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
|
||||
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
|
||||
}
|
||||
globalName := b.fn.Package().Pkg.Path() + "$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
||||
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
global.SetVisibility(llvm.HiddenVisibility)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
initializer := llvm.ConstNull(globalLLVMType)
|
||||
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
|
||||
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0})
|
||||
initializer = llvm.ConstInsertValue(initializer, funcContext, []uint32{0})
|
||||
initializer = llvm.ConstInsertValue(initializer, funcPtr, []uint32{1})
|
||||
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{2, 0})
|
||||
global.SetInitializer(initializer)
|
||||
|
||||
// Add debug info to the interrupt global.
|
||||
|
||||
@@ -48,3 +48,57 @@ func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
|
||||
b.CreateCall(llvmFn, params, "")
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
|
||||
var mathToLLVMMapping = map[string]string{
|
||||
"math.Sqrt": "llvm.sqrt.f64",
|
||||
"math.Floor": "llvm.floor.f64",
|
||||
"math.Ceil": "llvm.ceil.f64",
|
||||
"math.Trunc": "llvm.trunc.f64",
|
||||
}
|
||||
|
||||
// createMathOp tries to lower the given call as a LLVM math intrinsic, if
|
||||
// possible. It returns the call result if possible, and a boolean whether it
|
||||
// succeeded. If it doesn't succeed, the architecture doesn't support the given
|
||||
// intrinsic.
|
||||
func (b *builder) createMathOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
||||
// Check whether this intrinsic is supported on the given GOARCH.
|
||||
// If it is unsupported, this can have two reasons:
|
||||
//
|
||||
// 1. LLVM can expand the intrinsic inline (using float instructions), but
|
||||
// the result doesn't pass the tests of the math package.
|
||||
// 2. LLVM cannot expand the intrinsic inline, will therefore lower it as a
|
||||
// libm function call, but the libm function call also fails the math
|
||||
// package tests.
|
||||
//
|
||||
// Whatever the implementation, it must pass the tests in the math package
|
||||
// so unfortunately only the below intrinsic+architecture combinations are
|
||||
// supported.
|
||||
name := call.StaticCallee().RelString(nil)
|
||||
switch name {
|
||||
case "math.Ceil", "math.Floor", "math.Trunc":
|
||||
if b.GOARCH != "wasm" && b.GOARCH != "arm64" {
|
||||
return llvm.Value{}, false
|
||||
}
|
||||
case "math.Sqrt":
|
||||
if b.GOARCH != "wasm" && b.GOARCH != "amd64" && b.GOARCH != "386" {
|
||||
return llvm.Value{}, false
|
||||
}
|
||||
default:
|
||||
return llvm.Value{}, false // only the above functions are supported.
|
||||
}
|
||||
|
||||
llvmFn := b.mod.NamedFunction(mathToLLVMMapping[name])
|
||||
if llvmFn.IsNil() {
|
||||
// The intrinsic doesn't exist yet, so declare it.
|
||||
// At the moment, all supported intrinsics have the form "double
|
||||
// foo(double %x)" so we can hardcode the signature here.
|
||||
llvmType := llvm.FunctionType(b.ctx.DoubleType(), []llvm.Type{b.ctx.DoubleType()}, false)
|
||||
llvmFn = llvm.AddFunction(b.mod, mathToLLVMMapping[name], llvmType)
|
||||
}
|
||||
// Create a call to the intrinsic.
|
||||
args := make([]llvm.Value, len(call.Args))
|
||||
for i, arg := range call.Args {
|
||||
args[i] = b.getValue(arg)
|
||||
}
|
||||
return b.CreateCall(llvmFn, args, ""), true
|
||||
}
|
||||
|
||||
+202
-16
@@ -1,6 +1,11 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"math/big"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -8,21 +13,6 @@ import (
|
||||
// This file contains helper functions for LLVM that are not exposed in the Go
|
||||
// bindings.
|
||||
|
||||
// Return a list of values (actually, instructions) where this value is used as
|
||||
// an operand.
|
||||
func getUses(value llvm.Value) []llvm.Value {
|
||||
if value.IsNil() {
|
||||
return nil
|
||||
}
|
||||
var uses []llvm.Value
|
||||
use := value.FirstUse()
|
||||
for !use.IsNil() {
|
||||
uses = append(uses, use.User())
|
||||
use = use.NextUse()
|
||||
}
|
||||
return uses
|
||||
}
|
||||
|
||||
// createTemporaryAlloca creates a new alloca in the entry block and adds
|
||||
// lifetime start information in the IR signalling that the alloca won't be used
|
||||
// before this point.
|
||||
@@ -44,7 +34,7 @@ func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
|
||||
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
||||
// pointer value directly. It returns the pointer with the packed data.
|
||||
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.NeedsStackObjects, values)
|
||||
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.pkg.Path(), b.NeedsStackObjects, values)
|
||||
}
|
||||
|
||||
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
||||
@@ -67,3 +57,199 @@ func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType l
|
||||
global.SetInitializer(value)
|
||||
return global
|
||||
}
|
||||
|
||||
// createObjectLayout returns a LLVM value (of type i8*) that describes where
|
||||
// there are pointers in the type t. If all the data fits in a word, it is
|
||||
// returned as a word. Otherwise it will store the data in a global.
|
||||
//
|
||||
// The value contains two pieces of information: the length of the object and
|
||||
// which words contain a pointer (indicated by setting the given bit to 1). For
|
||||
// arrays, only the element is stored. This works because the GC knows the
|
||||
// object size and can therefore know how this value is repeated in the object.
|
||||
func (c *compilerContext) createObjectLayout(t llvm.Type, pos token.Pos) llvm.Value {
|
||||
// Use the element type for arrays. This works even for nested arrays.
|
||||
for {
|
||||
kind := t.TypeKind()
|
||||
if kind == llvm.ArrayTypeKind {
|
||||
t = t.ElementType()
|
||||
continue
|
||||
}
|
||||
if kind == llvm.StructTypeKind {
|
||||
fields := t.StructElementTypes()
|
||||
if len(fields) == 1 {
|
||||
t = fields[0]
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Do a few checks to see whether we need to generate any object layout
|
||||
// information at all.
|
||||
objectSizeBytes := c.targetData.TypeAllocSize(t)
|
||||
pointerSize := c.targetData.TypeAllocSize(c.i8ptrType)
|
||||
pointerAlignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
||||
if objectSizeBytes < pointerSize {
|
||||
// Too small to contain a pointer.
|
||||
layout := (uint64(1) << 1) | 1
|
||||
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
|
||||
}
|
||||
bitmap := c.getPointerBitmap(t, pos)
|
||||
if bitmap.BitLen() == 0 {
|
||||
// There are no pointers in this type, so we can simplify the layout.
|
||||
// TODO: this can be done in many other cases, e.g. when allocating an
|
||||
// array (like [4][]byte, which repeats a slice 4 times).
|
||||
layout := (uint64(1) << 1) | 1
|
||||
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
|
||||
}
|
||||
if objectSizeBytes%uint64(pointerAlignment) != 0 {
|
||||
// This shouldn't happen except for packed structs, which aren't
|
||||
// currently used.
|
||||
c.addError(pos, "internal error: unexpected object size for object with pointer field")
|
||||
return llvm.ConstNull(c.i8ptrType)
|
||||
}
|
||||
objectSizeWords := objectSizeBytes / uint64(pointerAlignment)
|
||||
|
||||
pointerBits := pointerSize * 8
|
||||
var sizeFieldBits uint64
|
||||
switch pointerBits {
|
||||
case 16:
|
||||
sizeFieldBits = 4
|
||||
case 32:
|
||||
sizeFieldBits = 5
|
||||
case 64:
|
||||
sizeFieldBits = 6
|
||||
default:
|
||||
panic("unknown pointer size")
|
||||
}
|
||||
layoutFieldBits := pointerBits - 1 - sizeFieldBits
|
||||
|
||||
// Try to emit the value as an inline integer. This is possible in most
|
||||
// cases.
|
||||
if objectSizeWords < layoutFieldBits {
|
||||
// If it can be stored directly in the pointer value, do so.
|
||||
// The runtime knows that if the least significant bit of the pointer is
|
||||
// set, the pointer contains the value itself.
|
||||
layout := bitmap.Uint64()<<(sizeFieldBits+1) | (objectSizeWords << 1) | 1
|
||||
return llvm.ConstIntToPtr(llvm.ConstInt(c.uintptrType, layout, false), c.i8ptrType)
|
||||
}
|
||||
|
||||
// Unfortunately, the object layout is too big to fit in a pointer-sized
|
||||
// integer. Store it in a global instead.
|
||||
|
||||
// Try first whether the global already exists. All objects with a
|
||||
// particular name have the same type, so this is possible.
|
||||
globalName := "runtime/gc.layout:" + fmt.Sprintf("%d-%0*x", objectSizeWords, (objectSizeWords+15)/16, bitmap)
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if !global.IsNil() {
|
||||
return llvm.ConstBitCast(global, c.i8ptrType)
|
||||
}
|
||||
|
||||
// Create the global initializer.
|
||||
bitmapBytes := make([]byte, int(objectSizeWords+7)/8)
|
||||
bitmap.FillBytes(bitmapBytes)
|
||||
var bitmapByteValues []llvm.Value
|
||||
for _, b := range bitmapBytes {
|
||||
bitmapByteValues = append(bitmapByteValues, llvm.ConstInt(c.ctx.Int8Type(), uint64(b), false))
|
||||
}
|
||||
initializer := c.ctx.ConstStruct([]llvm.Value{
|
||||
llvm.ConstInt(c.uintptrType, objectSizeWords, false),
|
||||
llvm.ConstArray(c.ctx.Int8Type(), bitmapByteValues),
|
||||
}, false)
|
||||
|
||||
global = llvm.AddGlobal(c.mod, initializer.Type(), globalName)
|
||||
global.SetInitializer(initializer)
|
||||
global.SetUnnamedAddr(true)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
if c.targetData.PrefTypeAlignment(c.uintptrType) < 2 {
|
||||
// AVR doesn't have alignment by default.
|
||||
global.SetAlignment(2)
|
||||
}
|
||||
if c.Debug && pos != token.NoPos {
|
||||
// Creating a fake global so that the value can be inspected in GDB.
|
||||
// For example, the layout for strings.stringFinder (as of Go version
|
||||
// 1.15) has the following type according to GDB:
|
||||
// type = struct {
|
||||
// uintptr numBits;
|
||||
// uint8 data[33];
|
||||
// }
|
||||
// ...that's sort of a mixed C/Go type, but it is readable. More
|
||||
// importantly, these object layout globals can be read and printed by
|
||||
// GDB which may be useful for debugging.
|
||||
position := c.program.Fset.Position(pos)
|
||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[position.Filename], llvm.DIGlobalVariableExpression{
|
||||
Name: globalName,
|
||||
File: c.getDIFile(position.Filename),
|
||||
Line: position.Line,
|
||||
Type: c.getDIType(types.NewStruct([]*types.Var{
|
||||
types.NewVar(pos, nil, "numBits", types.Typ[types.Uintptr]),
|
||||
types.NewVar(pos, nil, "data", types.NewArray(types.Typ[types.Byte], int64(len(bitmapByteValues)))),
|
||||
}, nil)),
|
||||
LocalToUnit: false,
|
||||
Expr: c.dibuilder.CreateExpression(nil),
|
||||
})
|
||||
global.AddMetadata(0, diglobal)
|
||||
}
|
||||
|
||||
return llvm.ConstBitCast(global, c.i8ptrType)
|
||||
}
|
||||
|
||||
// getPointerBitmap scans the given LLVM type for pointers and sets bits in a
|
||||
// bigint at the word offset that contains a pointer. This scan is recursive.
|
||||
func (c *compilerContext) getPointerBitmap(typ llvm.Type, pos token.Pos) *big.Int {
|
||||
alignment := c.targetData.PrefTypeAlignment(c.i8ptrType)
|
||||
switch typ.TypeKind() {
|
||||
case llvm.IntegerTypeKind, llvm.FloatTypeKind, llvm.DoubleTypeKind:
|
||||
return big.NewInt(0)
|
||||
case llvm.PointerTypeKind:
|
||||
return big.NewInt(1)
|
||||
case llvm.StructTypeKind:
|
||||
ptrs := big.NewInt(0)
|
||||
if typ.StructName() == "runtime.funcValue" {
|
||||
// Hack: the type runtime.funcValue contains an 'id' field which is
|
||||
// of type uintptr, but before the LowerFuncValues pass it actually
|
||||
// contains a pointer (ptrtoint) to a global. This trips up the
|
||||
// interp package. Therefore, make the id field a pointer for now.
|
||||
typ = c.ctx.StructType([]llvm.Type{c.i8ptrType, c.i8ptrType}, false)
|
||||
}
|
||||
for i, subtyp := range typ.StructElementTypes() {
|
||||
subptrs := c.getPointerBitmap(subtyp, pos)
|
||||
if subptrs.BitLen() == 0 {
|
||||
continue
|
||||
}
|
||||
offset := c.targetData.ElementOffset(typ, i)
|
||||
if offset%uint64(alignment) != 0 {
|
||||
// This error will let the compilation fail, but by continuing
|
||||
// the error can still easily be shown.
|
||||
c.addError(pos, "internal error: allocated struct contains unaligned pointer")
|
||||
continue
|
||||
}
|
||||
subptrs.Lsh(subptrs, uint(offset)/uint(alignment))
|
||||
ptrs.Or(ptrs, subptrs)
|
||||
}
|
||||
return ptrs
|
||||
case llvm.ArrayTypeKind:
|
||||
subtyp := typ.ElementType()
|
||||
subptrs := c.getPointerBitmap(subtyp, pos)
|
||||
ptrs := big.NewInt(0)
|
||||
if subptrs.BitLen() == 0 {
|
||||
return ptrs
|
||||
}
|
||||
elementSize := c.targetData.TypeAllocSize(subtyp)
|
||||
if elementSize%uint64(alignment) != 0 {
|
||||
// This error will let the compilation fail (but continues so that
|
||||
// other errors can be shown).
|
||||
c.addError(pos, "internal error: allocated array contains unaligned pointer")
|
||||
return ptrs
|
||||
}
|
||||
for i := 0; i < typ.ArrayLength(); i++ {
|
||||
ptrs.Lsh(ptrs, uint(elementSize)/uint(alignment))
|
||||
ptrs.Or(ptrs, subptrs)
|
||||
}
|
||||
return ptrs
|
||||
default:
|
||||
// Should not happen.
|
||||
panic("unknown LLVM type")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// bitcasts, or else allocates a value on the heap if it cannot be packed in the
|
||||
// pointer value directly. It returns the pointer with the packed data.
|
||||
// If the values are all constants, they are be stored in a constant global and deduplicated.
|
||||
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bool, values []llvm.Value) llvm.Value {
|
||||
func EmitPointerPack(builder llvm.Builder, mod llvm.Module, prefix string, needsStackObjects bool, values []llvm.Value) llvm.Value {
|
||||
ctx := mod.Context()
|
||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||
@@ -83,12 +83,11 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bo
|
||||
if constant {
|
||||
// The data is known at compile time, so store it in a constant global.
|
||||
// The global address is marked as unnamed, which allows LLVM to merge duplicates.
|
||||
funcName := builder.GetInsertBlock().Parent().Name()
|
||||
global := llvm.AddGlobal(mod, packedType, funcName+"$pack")
|
||||
global := llvm.AddGlobal(mod, packedType, prefix+"$pack")
|
||||
global.SetInitializer(ctx.ConstStruct(values, false))
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
return llvm.ConstBitCast(global, i8ptrType)
|
||||
}
|
||||
|
||||
@@ -97,15 +96,14 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, needsStackObjects bo
|
||||
alloc := mod.NamedFunction("runtime.alloc")
|
||||
packedHeapAlloc := builder.CreateCall(alloc, []llvm.Value{
|
||||
sizeValue,
|
||||
llvm.Undef(i8ptrType), // unused context parameter
|
||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||
llvm.ConstNull(i8ptrType),
|
||||
llvm.Undef(i8ptrType), // unused context parameter
|
||||
}, "")
|
||||
if needsStackObjects {
|
||||
trackPointer := mod.NamedFunction("runtime.trackPointer")
|
||||
builder.CreateCall(trackPointer, []llvm.Value{
|
||||
packedHeapAlloc,
|
||||
llvm.Undef(i8ptrType), // unused context parameter
|
||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||
llvm.Undef(i8ptrType), // unused context parameter
|
||||
}, "")
|
||||
}
|
||||
packedAlloc := builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||
|
||||
@@ -167,6 +167,66 @@ func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos tok
|
||||
}
|
||||
}
|
||||
|
||||
// createMapIteratorNext lowers the *ssa.Next instruction for iterating over a
|
||||
// map. It returns a tuple of {bool, key, value} with the result of the
|
||||
// iteration.
|
||||
func (b *builder) createMapIteratorNext(rangeVal ssa.Value, llvmRangeVal, it llvm.Value) llvm.Value {
|
||||
// Determine the type of the values to return from the *ssa.Next
|
||||
// instruction. It is returned as {bool, keyType, valueType}.
|
||||
keyType := rangeVal.Type().Underlying().(*types.Map).Key()
|
||||
valueType := rangeVal.Type().Underlying().(*types.Map).Elem()
|
||||
llvmKeyType := b.getLLVMType(keyType)
|
||||
llvmValueType := b.getLLVMType(valueType)
|
||||
|
||||
// There is a special case in which keys are stored as an interface value
|
||||
// instead of the value they normally are. This happens for non-trivially
|
||||
// comparable types such as float32 or some structs.
|
||||
isKeyStoredAsInterface := false
|
||||
if t, ok := keyType.Underlying().(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// key can be compared with runtime.memequal
|
||||
} else {
|
||||
// The key is stored as an interface value, and may or may not be an
|
||||
// interface type (for example, float32 keys are stored as an interface
|
||||
// value).
|
||||
if _, ok := keyType.Underlying().(*types.Interface); !ok {
|
||||
isKeyStoredAsInterface = true
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the type of the key as stored in the map.
|
||||
llvmStoredKeyType := llvmKeyType
|
||||
if isKeyStoredAsInterface {
|
||||
llvmStoredKeyType = b.getLLVMRuntimeType("_interface")
|
||||
}
|
||||
|
||||
// Extract the key and value from the map.
|
||||
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(llvmStoredKeyType, "range.key")
|
||||
mapValueAlloca, mapValuePtr, mapValueSize := b.createTemporaryAlloca(llvmValueType, "range.value")
|
||||
ok := b.createRuntimeCall("hashmapNext", []llvm.Value{llvmRangeVal, it, mapKeyPtr, mapValuePtr}, "range.next")
|
||||
mapKey := b.CreateLoad(mapKeyAlloca, "")
|
||||
mapValue := b.CreateLoad(mapValueAlloca, "")
|
||||
|
||||
if isKeyStoredAsInterface {
|
||||
// The key is stored as an interface but it isn't of interface type.
|
||||
// Extract the underlying value.
|
||||
mapKey = b.extractValueFromInterface(mapKey, llvmKeyType)
|
||||
}
|
||||
|
||||
// End the lifetimes of the allocas, because we're done with them.
|
||||
b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
||||
b.emitLifetimeEnd(mapValuePtr, mapValueSize)
|
||||
|
||||
// Construct the *ssa.Next return value: {ok, mapKey, mapValue}
|
||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{b.ctx.Int1Type(), llvmKeyType, llvmValueType}, false))
|
||||
tuple = b.CreateInsertValue(tuple, ok, 0, "")
|
||||
tuple = b.CreateInsertValue(tuple, mapKey, 1, "")
|
||||
tuple = b.CreateInsertValue(tuple, mapValue, 2, "")
|
||||
|
||||
return tuple
|
||||
}
|
||||
|
||||
// Returns true if this key type does not contain strings, interfaces etc., so
|
||||
// can be compared with runtime.memequal.
|
||||
func hashmapIsBinaryKey(keyType types.Type) bool {
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
package compiler
|
||||
|
||||
// This file implements a simple reachability analysis, to reduce compile time.
|
||||
// This DCE pass used to be necessary for improving other passes but now it
|
||||
// isn't necessary anymore.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"go/types"
|
||||
"sort"
|
||||
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
)
|
||||
|
||||
type dceState struct {
|
||||
*compilerContext
|
||||
functions []*dceFunction
|
||||
functionMap map[*ssa.Function]*dceFunction
|
||||
}
|
||||
|
||||
type dceFunction struct {
|
||||
*ssa.Function
|
||||
functionInfo
|
||||
flag bool // used by dead code elimination
|
||||
}
|
||||
|
||||
func (p *dceState) addFunction(ssaFn *ssa.Function) {
|
||||
if _, ok := p.functionMap[ssaFn]; ok {
|
||||
return
|
||||
}
|
||||
f := &dceFunction{Function: ssaFn}
|
||||
f.functionInfo = p.getFunctionInfo(ssaFn)
|
||||
p.functions = append(p.functions, f)
|
||||
p.functionMap[ssaFn] = f
|
||||
|
||||
for _, anon := range ssaFn.AnonFuncs {
|
||||
p.addFunction(anon)
|
||||
}
|
||||
}
|
||||
|
||||
// simpleDCE returns a list of alive functions in the program. Compiling only
|
||||
// these functions makes the compiler faster.
|
||||
//
|
||||
// This functionality will likely be replaced in the future with build caching.
|
||||
func (c *compilerContext) simpleDCE(lprogram *loader.Program) ([]*ssa.Function, error) {
|
||||
mainPkg := c.program.Package(lprogram.MainPkg().Pkg)
|
||||
if mainPkg == nil {
|
||||
panic("could not find main package")
|
||||
}
|
||||
p := &dceState{
|
||||
compilerContext: c,
|
||||
functionMap: make(map[*ssa.Function]*dceFunction),
|
||||
}
|
||||
|
||||
for _, pkg := range lprogram.Sorted() {
|
||||
pkg := c.program.Package(pkg.Pkg)
|
||||
memberNames := make([]string, 0)
|
||||
for name := range pkg.Members {
|
||||
memberNames = append(memberNames, name)
|
||||
}
|
||||
sort.Strings(memberNames)
|
||||
|
||||
for _, name := range memberNames {
|
||||
member := pkg.Members[name]
|
||||
switch member := member.(type) {
|
||||
case *ssa.Function:
|
||||
p.addFunction(member)
|
||||
case *ssa.Type:
|
||||
methods := getAllMethods(pkg.Prog, member.Type())
|
||||
if !types.IsInterface(member.Type()) {
|
||||
// named type
|
||||
for _, method := range methods {
|
||||
p.addFunction(pkg.Prog.MethodValue(method))
|
||||
}
|
||||
}
|
||||
case *ssa.Global:
|
||||
// Ignore. Globals are not handled here.
|
||||
case *ssa.NamedConst:
|
||||
// Ignore: these are already resolved.
|
||||
default:
|
||||
panic("unknown member type: " + member.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initial set of live functions. Include main.main, *.init and runtime.*
|
||||
// functions.
|
||||
main, ok := mainPkg.Members["main"].(*ssa.Function)
|
||||
if !ok {
|
||||
if mainPkg.Members["main"] == nil {
|
||||
return nil, errors.New("function main is undeclared in the main package")
|
||||
} else {
|
||||
return nil, errors.New("cannot declare main - must be func")
|
||||
}
|
||||
}
|
||||
runtimePkg := c.program.ImportedPackage("runtime")
|
||||
mathPkg := c.program.ImportedPackage("math")
|
||||
taskPkg := c.program.ImportedPackage("internal/task")
|
||||
p.functionMap[main].flag = true
|
||||
worklist := []*ssa.Function{main}
|
||||
for _, f := range p.functions {
|
||||
if f.exported || f.Synthetic == "package initializer" || f.Pkg == runtimePkg || f.Pkg == taskPkg || (f.Pkg == mathPkg && f.Pkg != nil) {
|
||||
if f.flag {
|
||||
continue
|
||||
}
|
||||
f.flag = true
|
||||
worklist = append(worklist, f.Function)
|
||||
}
|
||||
}
|
||||
|
||||
// Mark all called functions recursively.
|
||||
for len(worklist) != 0 {
|
||||
f := worklist[len(worklist)-1]
|
||||
worklist = worklist[:len(worklist)-1]
|
||||
for _, block := range f.Blocks {
|
||||
for _, instr := range block.Instrs {
|
||||
if instr, ok := instr.(*ssa.MakeInterface); ok {
|
||||
for _, sel := range getAllMethods(c.program, instr.X.Type()) {
|
||||
fn := c.program.MethodValue(sel)
|
||||
callee := p.functionMap[fn]
|
||||
if callee == nil {
|
||||
// TODO: why is this necessary?
|
||||
p.addFunction(fn)
|
||||
callee = p.functionMap[fn]
|
||||
}
|
||||
if !callee.flag {
|
||||
callee.flag = true
|
||||
worklist = append(worklist, callee.Function)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, operand := range instr.Operands(nil) {
|
||||
if operand == nil || *operand == nil {
|
||||
continue
|
||||
}
|
||||
switch operand := (*operand).(type) {
|
||||
case *ssa.Function:
|
||||
f := p.functionMap[operand]
|
||||
if f == nil {
|
||||
// FIXME HACK: this function should have been
|
||||
// discovered already. It is not for bound methods.
|
||||
p.addFunction(operand)
|
||||
f = p.functionMap[operand]
|
||||
}
|
||||
if !f.flag {
|
||||
f.flag = true
|
||||
worklist = append(worklist, operand)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return all live functions.
|
||||
liveFunctions := []*ssa.Function{}
|
||||
for _, f := range p.functions {
|
||||
if f.flag {
|
||||
liveFunctions = append(liveFunctions, f.Function)
|
||||
}
|
||||
}
|
||||
return liveFunctions, nil
|
||||
}
|
||||
@@ -150,6 +150,9 @@ func (s *stdSizes) Sizeof(T types.Type) int64 {
|
||||
return s.PtrSize * 2
|
||||
case *types.Pointer:
|
||||
return s.PtrSize
|
||||
case *types.Signature:
|
||||
// Func values in TinyGo are two words in size.
|
||||
return s.PtrSize * 2
|
||||
default:
|
||||
panic("unknown type: " + t.String())
|
||||
}
|
||||
|
||||
+137
-53
@@ -24,7 +24,9 @@ type functionInfo struct {
|
||||
module string // go:wasm-module
|
||||
importName string // go:linkname, go:export - The name the developer assigns
|
||||
linkName string // go:linkname, go:export - The name that we map for the particular module -> importName
|
||||
section string // go:section - object file section name
|
||||
exported bool // go:export, CGo
|
||||
interrupt bool // go:interrupt
|
||||
nobounds bool // go:nobounds
|
||||
variadic bool // go:variadic (CGo only)
|
||||
inline inlineType // go:inline
|
||||
@@ -72,9 +74,9 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
||||
}
|
||||
|
||||
var paramInfos []paramInfo
|
||||
for _, param := range fn.Params {
|
||||
for _, param := range getParams(fn.Signature) {
|
||||
paramType := c.getLLVMType(param.Type())
|
||||
paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
|
||||
paramFragmentInfos := c.expandFormalParamType(paramType, param.Name(), param.Type())
|
||||
paramInfos = append(paramInfos, paramFragmentInfos...)
|
||||
}
|
||||
|
||||
@@ -82,7 +84,6 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
||||
// closures and bound methods, but should be optimized away when not used.
|
||||
if !info.exported {
|
||||
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "context", flags: 0})
|
||||
paramInfos = append(paramInfos, paramInfo{llvmType: c.i8ptrType, name: "parentHandle", flags: 0})
|
||||
}
|
||||
|
||||
var paramTypes []llvm.Type
|
||||
@@ -107,6 +108,7 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
||||
llvmFn.AddFunctionAttr(attr)
|
||||
}
|
||||
}
|
||||
c.addStandardDeclaredAttributes(llvmFn)
|
||||
|
||||
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
|
||||
for i, info := range paramInfos {
|
||||
@@ -139,6 +141,17 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
||||
for _, attrName := range []string{"noalias", "nonnull"} {
|
||||
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 0))
|
||||
}
|
||||
case "runtime.sliceAppend":
|
||||
// Appending a slice will only read the to-be-appended slice, it won't
|
||||
// be modified.
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
case "runtime.sliceCopy":
|
||||
// Copying a slice won't capture any of the parameters.
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("writeonly"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
llvmFn.AddAttributeAtIndex(2, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
case "runtime.trackPointer":
|
||||
// This function is necessary for tracking pointers on the stack in a
|
||||
// portable way (see gc_stack_portable.go). Indicate to the optimizer
|
||||
@@ -172,6 +185,21 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
||||
}
|
||||
}
|
||||
|
||||
// Synthetic functions are functions that do not appear in the source code,
|
||||
// they are artificially constructed. Usually they are wrapper functions
|
||||
// that are not referenced anywhere except in a SSA call instruction so
|
||||
// should be created right away.
|
||||
// The exception is the package initializer, which does appear in the
|
||||
// *ssa.Package members and so shouldn't be created here.
|
||||
if fn.Synthetic != "" && fn.Synthetic != "package initializer" {
|
||||
irbuilder := c.ctx.NewBuilder()
|
||||
b := newBuilder(c, irbuilder, fn)
|
||||
b.createFunction()
|
||||
irbuilder.Dispose()
|
||||
llvmFn.SetLinkage(llvm.LinkOnceODRLinkage)
|
||||
llvmFn.SetUnnamedAddr(true)
|
||||
}
|
||||
|
||||
return llvmFn
|
||||
}
|
||||
|
||||
@@ -179,14 +207,9 @@ func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
||||
// present in *ssa.Function, such as the link name and whether it should be
|
||||
// exported.
|
||||
func (c *compilerContext) getFunctionInfo(f *ssa.Function) functionInfo {
|
||||
info := functionInfo{}
|
||||
if strings.HasPrefix(f.Name(), "C.") {
|
||||
// Created by CGo: such a name cannot be created by regular C code.
|
||||
info.linkName = f.Name()[2:]
|
||||
info.exported = true
|
||||
} else {
|
||||
info := functionInfo{
|
||||
// Pick the default linkName.
|
||||
info.linkName = f.RelString(nil)
|
||||
linkName: f.RelString(nil),
|
||||
}
|
||||
// Check for //go: pragmas, which may change the link name (among others).
|
||||
info.parsePragmas(f)
|
||||
@@ -223,6 +246,10 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
|
||||
|
||||
importName = parts[1]
|
||||
info.exported = true
|
||||
case "//go:interrupt":
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.interrupt = true
|
||||
}
|
||||
case "//go:wasm-module":
|
||||
// Alternative comment for setting the import module.
|
||||
if len(parts) != 2 {
|
||||
@@ -244,6 +271,10 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.linkName = parts[2]
|
||||
}
|
||||
case "//go:section":
|
||||
if len(parts) == 2 && hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.section = parts[1]
|
||||
}
|
||||
case "//go:nobounds":
|
||||
// Skip bounds checking in this function. Useful for some
|
||||
// runtime functions.
|
||||
@@ -278,6 +309,67 @@ func (info *functionInfo) parsePragmas(f *ssa.Function) {
|
||||
}
|
||||
}
|
||||
|
||||
// getParams returns the function parameters, including the receiver at the
|
||||
// start. This is an alternative to the Params member of *ssa.Function, which is
|
||||
// not yet populated when the package has not yet been built.
|
||||
func getParams(sig *types.Signature) []*types.Var {
|
||||
params := []*types.Var{}
|
||||
if sig.Recv() != nil {
|
||||
params = append(params, sig.Recv())
|
||||
}
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
params = append(params, sig.Params().At(i))
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
// addStandardDeclaredAttributes adds attributes that are set for any function,
|
||||
// whether declared or defined.
|
||||
func (c *compilerContext) addStandardDeclaredAttributes(llvmFn llvm.Value) {
|
||||
if c.SizeLevel >= 1 {
|
||||
// Set the "optsize" attribute to make slightly smaller binaries at the
|
||||
// cost of minimal performance loss (-Os in Clang).
|
||||
kind := llvm.AttributeKindID("optsize")
|
||||
attr := c.ctx.CreateEnumAttribute(kind, 0)
|
||||
llvmFn.AddFunctionAttr(attr)
|
||||
}
|
||||
if c.SizeLevel >= 2 {
|
||||
// Set the "minsize" attribute to reduce code size even further,
|
||||
// regardless of performance loss (-Oz in Clang).
|
||||
kind := llvm.AttributeKindID("minsize")
|
||||
attr := c.ctx.CreateEnumAttribute(kind, 0)
|
||||
llvmFn.AddFunctionAttr(attr)
|
||||
}
|
||||
if c.CPU != "" {
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("target-cpu", c.CPU))
|
||||
}
|
||||
if c.Features != "" {
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("target-features", c.Features))
|
||||
}
|
||||
}
|
||||
|
||||
// addStandardDefinedAttributes adds the set of attributes that are added to
|
||||
// every function defined by TinyGo (even thunks/wrappers), possibly depending
|
||||
// on the architecture. It does not set attributes only set for declared
|
||||
// functions, use addStandardDeclaredAttributes for this.
|
||||
func (c *compilerContext) addStandardDefinedAttributes(llvmFn llvm.Value) {
|
||||
// TinyGo does not currently raise exceptions, so set the 'nounwind' flag.
|
||||
// This behavior matches Clang when compiling C source files.
|
||||
// It reduces binary size on Linux a little bit on non-x86_64 targets by
|
||||
// eliminating exception tables for these functions.
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nounwind"), 0))
|
||||
if strings.Split(c.Triple, "-")[0] == "x86_64" {
|
||||
// Required by the ABI.
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("uwtable"), 0))
|
||||
}
|
||||
}
|
||||
|
||||
// addStandardAttribute adds all attributes added to defined functions.
|
||||
func (c *compilerContext) addStandardAttributes(llvmFn llvm.Value) {
|
||||
c.addStandardDeclaredAttributes(llvmFn)
|
||||
c.addStandardDefinedAttributes(llvmFn)
|
||||
}
|
||||
|
||||
// globalInfo contains some information about a specific global. By default,
|
||||
// linkName is equal to .RelString(nil) on a global and extern is false, but for
|
||||
// some symbols this is different (due to //go:extern for example).
|
||||
@@ -285,29 +377,27 @@ type globalInfo struct {
|
||||
linkName string // go:extern
|
||||
extern bool // go:extern
|
||||
align int // go:align
|
||||
section string // go:section
|
||||
}
|
||||
|
||||
// loadASTComments loads comments on globals from the AST, for use later in the
|
||||
// program. In particular, they are required for //go:extern pragmas on globals.
|
||||
func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
|
||||
c.astComments = map[string]*ast.CommentGroup{}
|
||||
for _, pkgInfo := range lprogram.Sorted() {
|
||||
for _, file := range pkgInfo.Files {
|
||||
for _, decl := range file.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
switch decl.Tok {
|
||||
case token.VAR:
|
||||
if len(decl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
for _, spec := range decl.Specs {
|
||||
switch spec := spec.(type) {
|
||||
case *ast.ValueSpec: // decl.Tok == token.VAR
|
||||
for _, name := range spec.Names {
|
||||
id := pkgInfo.Pkg.Path() + "." + name.Name
|
||||
c.astComments[id] = decl.Doc
|
||||
}
|
||||
func (c *compilerContext) loadASTComments(pkg *loader.Package) {
|
||||
for _, file := range pkg.Files {
|
||||
for _, decl := range file.Decls {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
switch decl.Tok {
|
||||
case token.VAR:
|
||||
if len(decl.Specs) != 1 {
|
||||
continue
|
||||
}
|
||||
for _, spec := range decl.Specs {
|
||||
switch spec := spec.(type) {
|
||||
case *ast.ValueSpec: // decl.Tok == token.VAR
|
||||
for _, name := range spec.Names {
|
||||
id := pkg.Pkg.Path() + "." + name.Name
|
||||
c.astComments[id] = decl.Doc
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,29 +416,25 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
|
||||
typ := g.Type().(*types.Pointer).Elem()
|
||||
llvmType := c.getLLVMType(typ)
|
||||
llvmGlobal = llvm.AddGlobal(c.mod, llvmType, info.linkName)
|
||||
if !info.extern {
|
||||
llvmGlobal.SetInitializer(llvm.ConstNull(llvmType))
|
||||
llvmGlobal.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
|
||||
// Set alignment from the //go:align comment.
|
||||
var alignInBits uint32
|
||||
if info.align < 0 || info.align&(info.align-1) != 0 {
|
||||
alignment := c.targetData.ABITypeAlignment(llvmType)
|
||||
if info.align > alignment {
|
||||
alignment = info.align
|
||||
}
|
||||
if alignment <= 0 || alignment&(alignment-1) != 0 {
|
||||
// Check for power-of-two (or 0).
|
||||
// See: https://stackoverflow.com/a/108360
|
||||
c.addError(g.Pos(), "global variable alignment must be a positive power of two")
|
||||
} else {
|
||||
// Set the alignment only when it is a power of two.
|
||||
alignInBits = uint32(info.align) ^ uint32(info.align-1)
|
||||
if info.align > c.targetData.ABITypeAlignment(llvmType) {
|
||||
llvmGlobal.SetAlignment(info.align)
|
||||
}
|
||||
alignInBits = uint32(alignment) ^ uint32(alignment-1)
|
||||
llvmGlobal.SetAlignment(alignment)
|
||||
}
|
||||
|
||||
if c.Debug && !info.extern {
|
||||
// Add debug info.
|
||||
// TODO: this should be done for every global in the program, not just
|
||||
// the ones that are referenced from some code.
|
||||
pos := c.program.Fset.Position(g.Pos())
|
||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
||||
Name: g.RelString(nil),
|
||||
@@ -368,20 +454,14 @@ func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
|
||||
|
||||
// getGlobalInfo returns some information about a specific global.
|
||||
func (c *compilerContext) getGlobalInfo(g *ssa.Global) globalInfo {
|
||||
info := globalInfo{}
|
||||
if strings.HasPrefix(g.Name(), "C.") {
|
||||
// Created by CGo: such a name cannot be created by regular C code.
|
||||
info.linkName = g.Name()[2:]
|
||||
info.extern = true
|
||||
} else {
|
||||
info := globalInfo{
|
||||
// Pick the default linkName.
|
||||
info.linkName = g.RelString(nil)
|
||||
// Check for //go: pragmas, which may change the link name (among
|
||||
// others).
|
||||
doc := c.astComments[info.linkName]
|
||||
if doc != nil {
|
||||
info.parsePragmas(doc)
|
||||
}
|
||||
linkName: g.RelString(nil),
|
||||
}
|
||||
// Check for //go: pragmas, which may change the link name (among others).
|
||||
doc := c.astComments[info.linkName]
|
||||
if doc != nil {
|
||||
info.parsePragmas(doc)
|
||||
}
|
||||
return info
|
||||
}
|
||||
@@ -405,6 +485,10 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
|
||||
if err == nil {
|
||||
info.align = align
|
||||
}
|
||||
case "//go:section":
|
||||
if len(parts) == 2 {
|
||||
info.section = parts[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+90
-13
@@ -10,11 +10,11 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// createSyscall emits an inline system call instruction, depending on the
|
||||
// target OS/arch.
|
||||
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
// createRawSyscall creates a system call with the provided system call number
|
||||
// and returns the result as a single integer (the system call result). The
|
||||
// result is not further interpreted.
|
||||
func (b *builder) createRawSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
num := b.getValue(call.Args[0])
|
||||
var syscallResult llvm.Value
|
||||
switch {
|
||||
case b.GOARCH == "amd64":
|
||||
if b.GOOS == "darwin" {
|
||||
@@ -56,8 +56,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
}
|
||||
constraints += ",~{rcx},~{r11}"
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel, false)
|
||||
return b.CreateCall(target, args, ""), nil
|
||||
case b.GOARCH == "386" && b.GOOS == "linux":
|
||||
// Sources:
|
||||
// syscall(2) man page
|
||||
@@ -82,8 +82,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel, false)
|
||||
return b.CreateCall(target, args, ""), nil
|
||||
case b.GOARCH == "arm" && b.GOOS == "linux":
|
||||
// Implement the EABI system call convention for Linux.
|
||||
// Source: syscall(2) man page.
|
||||
@@ -114,8 +114,8 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
constraints += ",~{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
|
||||
return b.CreateCall(target, args, ""), nil
|
||||
case b.GOARCH == "arm64" && b.GOOS == "linux":
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
@@ -146,13 +146,22 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
}
|
||||
constraints += ",~{x16},~{x17}" // scratch registers
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0, false)
|
||||
return b.CreateCall(target, args, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
// createSyscall emits instructions for the syscall.Syscall* family of
|
||||
// functions, depending on the target OS/arch.
|
||||
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
switch b.GOOS {
|
||||
case "linux", "freebsd":
|
||||
case "linux":
|
||||
syscallResult, err := b.createRawSyscall(call)
|
||||
if err != nil {
|
||||
return syscallResult, err
|
||||
}
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
@@ -171,6 +180,10 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
case "darwin":
|
||||
syscallResult, err := b.createRawSyscall(call)
|
||||
if err != nil {
|
||||
return syscallResult, err
|
||||
}
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
// var err uintptr
|
||||
@@ -186,7 +199,71 @@ func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
retval = b.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
case "windows":
|
||||
// On Windows, syscall.Syscall* is basically just a function pointer
|
||||
// call. This is complicated in gc because of stack switching and the
|
||||
// different ABI, but easy in TinyGo: just call the function pointer.
|
||||
// The signature looks like this:
|
||||
// func Syscall(trap, nargs, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
|
||||
|
||||
// Prepare input values.
|
||||
var paramTypes []llvm.Type
|
||||
var params []llvm.Value
|
||||
for _, val := range call.Args[2:] {
|
||||
param := b.getValue(val)
|
||||
params = append(params, param)
|
||||
paramTypes = append(paramTypes, param.Type())
|
||||
}
|
||||
llvmType := llvm.FunctionType(b.uintptrType, paramTypes, false)
|
||||
fn := b.getValue(call.Args[0])
|
||||
fnPtr := b.CreateIntToPtr(fn, llvm.PointerType(llvmType, 0), "")
|
||||
|
||||
// Prepare some functions that will be called later.
|
||||
setLastError := b.mod.NamedFunction("SetLastError")
|
||||
if setLastError.IsNil() {
|
||||
llvmType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.ctx.Int32Type()}, false)
|
||||
setLastError = llvm.AddFunction(b.mod, "SetLastError", llvmType)
|
||||
}
|
||||
getLastError := b.mod.NamedFunction("GetLastError")
|
||||
if getLastError.IsNil() {
|
||||
llvmType := llvm.FunctionType(b.ctx.Int32Type(), nil, false)
|
||||
getLastError = llvm.AddFunction(b.mod, "GetLastError", llvmType)
|
||||
}
|
||||
|
||||
// Now do the actual call. Pseudocode:
|
||||
// SetLastError(0)
|
||||
// r1 = trap(a1, a2, a3, ...)
|
||||
// err = uintptr(GetLastError())
|
||||
// return r1, 0, err
|
||||
// Note that SetLastError/GetLastError could be replaced with direct
|
||||
// access to the thread control block, which is probably smaller and
|
||||
// faster. The Go runtime does this in assembly.
|
||||
b.CreateCall(setLastError, []llvm.Value{llvm.ConstNull(b.ctx.Int32Type())}, "")
|
||||
syscallResult := b.CreateCall(fnPtr, params, "")
|
||||
errResult := b.CreateCall(getLastError, nil, "err")
|
||||
if b.uintptrType != b.ctx.Int32Type() {
|
||||
errResult = b.CreateZExt(errResult, b.uintptrType, "err.uintptr")
|
||||
}
|
||||
|
||||
// Return r1, 0, err
|
||||
retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
|
||||
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
default:
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
// createRawSyscallNoError emits instructions for the Linux-specific
|
||||
// syscall.rawSyscallNoError function.
|
||||
func (b *builder) createRawSyscallNoError(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
syscallResult, err := b.createRawSyscall(call)
|
||||
if err != nil {
|
||||
return syscallResult, err
|
||||
}
|
||||
retval := llvm.ConstNull(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType}, false))
|
||||
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.uintptrType, 0, false), 1, "")
|
||||
return retval, nil
|
||||
}
|
||||
|
||||
Vendored
+31
@@ -10,6 +10,22 @@ func equalInt(x, y int) bool {
|
||||
return x == y
|
||||
}
|
||||
|
||||
func divInt(x, y int) int {
|
||||
return x / y
|
||||
}
|
||||
|
||||
func divUint(x, y uint) uint {
|
||||
return x / y
|
||||
}
|
||||
|
||||
func remInt(x, y int) int {
|
||||
return x % y
|
||||
}
|
||||
|
||||
func remUint(x, y uint) uint {
|
||||
return x % y
|
||||
}
|
||||
|
||||
func floatEQ(x, y float32) bool {
|
||||
return x == y
|
||||
}
|
||||
@@ -55,3 +71,18 @@ func complexMul(x, y complex64) complex64 {
|
||||
}
|
||||
|
||||
// TODO: complexDiv (requires runtime call)
|
||||
|
||||
// A type 'kv' also exists in function foo. Test that these two types don't
|
||||
// conflict with each other.
|
||||
type kv struct {
|
||||
v float32
|
||||
}
|
||||
|
||||
func foo(a *kv) {
|
||||
// Define a new 'kv' type.
|
||||
type kv struct {
|
||||
v byte
|
||||
}
|
||||
// Use this type.
|
||||
func(b *kv) {}(nil)
|
||||
}
|
||||
|
||||
Vendored
+122
-16
@@ -1,72 +1,161 @@
|
||||
; ModuleID = 'basic.go'
|
||||
source_filename = "basic.go"
|
||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||
target triple = "i686--linux"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
%main.kv = type { float }
|
||||
%main.kv.0 = type { i8 }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.trackPointer(i8* nocapture readonly, i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.init(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.addInt(i32 %x, i32 %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = add i32 %x, %y
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
define internal i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i1 @main.equalInt(i32 %x, i32 %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp eq i32 %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define internal i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.divInt(i32 %x, i32 %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp eq i32 %y, 0
|
||||
br i1 %0, label %divbyzero.throw, label %divbyzero.next
|
||||
|
||||
divbyzero.throw: ; preds = %entry
|
||||
call void @runtime.divideByZeroPanic(i8* undef) #0
|
||||
unreachable
|
||||
|
||||
divbyzero.next: ; preds = %entry
|
||||
%1 = icmp eq i32 %y, -1
|
||||
%2 = icmp eq i32 %x, -2147483648
|
||||
%3 = and i1 %1, %2
|
||||
%4 = select i1 %3, i32 1, i32 %y
|
||||
%5 = sdiv i32 %x, %4
|
||||
ret i32 %5
|
||||
}
|
||||
|
||||
declare void @runtime.divideByZeroPanic(i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.divUint(i32 %x, i32 %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp eq i32 %y, 0
|
||||
br i1 %0, label %divbyzero.throw, label %divbyzero.next
|
||||
|
||||
divbyzero.throw: ; preds = %entry
|
||||
call void @runtime.divideByZeroPanic(i8* undef) #0
|
||||
unreachable
|
||||
|
||||
divbyzero.next: ; preds = %entry
|
||||
%1 = udiv i32 %x, %y
|
||||
ret i32 %1
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.remInt(i32 %x, i32 %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp eq i32 %y, 0
|
||||
br i1 %0, label %divbyzero.throw, label %divbyzero.next
|
||||
|
||||
divbyzero.throw: ; preds = %entry
|
||||
call void @runtime.divideByZeroPanic(i8* undef) #0
|
||||
unreachable
|
||||
|
||||
divbyzero.next: ; preds = %entry
|
||||
%1 = icmp eq i32 %y, -1
|
||||
%2 = icmp eq i32 %x, -2147483648
|
||||
%3 = and i1 %1, %2
|
||||
%4 = select i1 %3, i32 1, i32 %y
|
||||
%5 = srem i32 %x, %4
|
||||
ret i32 %5
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.remUint(i32 %x, i32 %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp eq i32 %y, 0
|
||||
br i1 %0, label %divbyzero.throw, label %divbyzero.next
|
||||
|
||||
divbyzero.throw: ; preds = %entry
|
||||
call void @runtime.divideByZeroPanic(i8* undef) #0
|
||||
unreachable
|
||||
|
||||
divbyzero.next: ; preds = %entry
|
||||
%1 = urem i32 %x, %y
|
||||
ret i32 %1
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i1 @main.floatEQ(float %x, float %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = fcmp oeq float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define internal i1 @main.floatNE(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i1 @main.floatNE(float %x, float %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = fcmp une float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define internal i1 @main.floatLower(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i1 @main.floatLower(float %x, float %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = fcmp olt float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define internal i1 @main.floatLowerEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i1 @main.floatLowerEqual(float %x, float %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = fcmp ole float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define internal i1 @main.floatGreater(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i1 @main.floatGreater(float %x, float %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = fcmp ogt float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define internal i1 @main.floatGreaterEqual(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i1 @main.floatGreaterEqual(float %x, float %y, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = fcmp oge float %x, %y
|
||||
ret i1 %0
|
||||
}
|
||||
|
||||
define internal float @main.complexReal(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden float @main.complexReal(float %x.r, float %x.i, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret float %x.r
|
||||
}
|
||||
|
||||
define internal float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden float @main.complexImag(float %x.r, float %x.i, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret float %x.i
|
||||
}
|
||||
|
||||
define internal { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden { float, float } @main.complexAdd(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = fadd float %x.r, %y.r
|
||||
%1 = fadd float %x.i, %y.i
|
||||
@@ -75,7 +164,8 @@ entry:
|
||||
ret { float, float } %3
|
||||
}
|
||||
|
||||
define internal { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden { float, float } @main.complexSub(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = fsub float %x.r, %y.r
|
||||
%1 = fsub float %x.i, %y.i
|
||||
@@ -84,7 +174,8 @@ entry:
|
||||
ret { float, float } %3
|
||||
}
|
||||
|
||||
define internal { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden { float, float } @main.complexMul(float %x.r, float %x.i, float %y.r, float %y.i, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = fmul float %x.r, %y.r
|
||||
%1 = fmul float %x.i, %y.i
|
||||
@@ -96,3 +187,18 @@ entry:
|
||||
%7 = insertvalue { float, float } %6, float %5, 1
|
||||
ret { float, float } %7
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.foo(%main.kv* dereferenceable_or_null(4) %a, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
call void @"main.foo$1"(%main.kv.0* null, i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @"main.foo$1"(%main.kv.0* dereferenceable_or_null(1) %b, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
attributes #0 = { nounwind }
|
||||
|
||||
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
func chanIntSend(ch chan int) {
|
||||
ch <- 3
|
||||
}
|
||||
|
||||
func chanIntRecv(ch chan int) {
|
||||
<-ch
|
||||
}
|
||||
|
||||
func chanZeroSend(ch chan struct{}) {
|
||||
ch <- struct{}{}
|
||||
}
|
||||
|
||||
func chanZeroRecv(ch chan struct{}) {
|
||||
<-ch
|
||||
}
|
||||
|
||||
func selectZeroRecv(ch1 chan int, ch2 chan struct{}) {
|
||||
select {
|
||||
case ch1 <- 1:
|
||||
case <-ch2:
|
||||
default:
|
||||
}
|
||||
}
|
||||
Vendored
+128
@@ -0,0 +1,128 @@
|
||||
; ModuleID = 'channel.go'
|
||||
source_filename = "channel.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
%runtime.channel = type { i32, i32, i8, %runtime.channelBlockedList*, i32, i32, i32, i8* }
|
||||
%runtime.channelBlockedList = type { %runtime.channelBlockedList*, %"internal/task.Task"*, %runtime.chanSelectState*, { %runtime.channelBlockedList*, i32, i32 } }
|
||||
%"internal/task.Task" = type { %"internal/task.Task"*, i8*, i64, %"internal/task.gcData", %"internal/task.state" }
|
||||
%"internal/task.gcData" = type { i8* }
|
||||
%"internal/task.state" = type { i32, i8*, %"internal/task.stackState", i1 }
|
||||
%"internal/task.stackState" = type { i32, i32 }
|
||||
%runtime.chanSelectState = type { %runtime.channel*, i8* }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.trackPointer(i8* nocapture readonly, i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.init(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.chanIntSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
%chan.value = alloca i32, align 4
|
||||
%chan.value.bitcast = bitcast i32* %chan.value to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
|
||||
store i32 3, i32* %chan.value, align 4
|
||||
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
call void @runtime.chanSend(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef) #0
|
||||
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: argmemonly nofree nosync nounwind willreturn
|
||||
declare void @llvm.lifetime.start.p0i8(i64 immarg, i8* nocapture) #1
|
||||
|
||||
declare void @runtime.chanSend(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*)
|
||||
|
||||
; Function Attrs: argmemonly nofree nosync nounwind willreturn
|
||||
declare void @llvm.lifetime.end.p0i8(i64 immarg, i8* nocapture) #1
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.chanIntRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
%chan.value = alloca i32, align 4
|
||||
%chan.value.bitcast = bitcast i32* %chan.value to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
|
||||
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* nonnull %chan.value.bitcast, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef) #0
|
||||
call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %chan.value.bitcast)
|
||||
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
ret void
|
||||
}
|
||||
|
||||
declare i1 @runtime.chanRecv(%runtime.channel* dereferenceable_or_null(32), i8*, %runtime.channelBlockedList* dereferenceable_or_null(24), i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.chanZeroSend(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%complit = alloca {}, align 8
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
%0 = bitcast {}* %complit to i8*
|
||||
call void @runtime.trackPointer(i8* nonnull %0, i8* undef) #0
|
||||
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
call void @runtime.chanSend(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef) #0
|
||||
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.chanZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%chan.blockedList = alloca %runtime.channelBlockedList, align 8
|
||||
%chan.blockedList.bitcast = bitcast %runtime.channelBlockedList* %chan.blockedList to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
%0 = call i1 @runtime.chanRecv(%runtime.channel* %ch, i8* null, %runtime.channelBlockedList* nonnull %chan.blockedList, i8* undef) #0
|
||||
call void @llvm.lifetime.end.p0i8(i64 24, i8* nonnull %chan.blockedList.bitcast)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.selectZeroRecv(%runtime.channel* dereferenceable_or_null(32) %ch1, %runtime.channel* dereferenceable_or_null(32) %ch2, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%select.states.alloca = alloca [2 x %runtime.chanSelectState], align 8
|
||||
%select.send.value = alloca i32, align 4
|
||||
store i32 1, i32* %select.send.value, align 4
|
||||
%select.states.alloca.bitcast = bitcast [2 x %runtime.chanSelectState]* %select.states.alloca to i8*
|
||||
call void @llvm.lifetime.start.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
|
||||
%.repack = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 0
|
||||
store %runtime.channel* %ch1, %runtime.channel** %.repack, align 8
|
||||
%.repack1 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0, i32 1
|
||||
%0 = bitcast i8** %.repack1 to i32**
|
||||
store i32* %select.send.value, i32** %0, align 4
|
||||
%.repack3 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 0
|
||||
store %runtime.channel* %ch2, %runtime.channel** %.repack3, align 8
|
||||
%.repack4 = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 1, i32 1
|
||||
store i8* null, i8** %.repack4, align 4
|
||||
%select.states = getelementptr inbounds [2 x %runtime.chanSelectState], [2 x %runtime.chanSelectState]* %select.states.alloca, i32 0, i32 0
|
||||
%select.result = call { i32, i1 } @runtime.tryChanSelect(i8* undef, %runtime.chanSelectState* nonnull %select.states, i32 2, i32 2, i8* undef) #0
|
||||
call void @llvm.lifetime.end.p0i8(i64 16, i8* nonnull %select.states.alloca.bitcast)
|
||||
%1 = extractvalue { i32, i1 } %select.result, 0
|
||||
%2 = icmp eq i32 %1, 0
|
||||
br i1 %2, label %select.done, label %select.next
|
||||
|
||||
select.done: ; preds = %select.body, %select.next, %entry
|
||||
ret void
|
||||
|
||||
select.next: ; preds = %entry
|
||||
%3 = icmp eq i32 %1, 1
|
||||
br i1 %3, label %select.body, label %select.done
|
||||
|
||||
select.body: ; preds = %select.next
|
||||
br label %select.done
|
||||
}
|
||||
|
||||
declare { i32, i1 } @runtime.tryChanSelect(i8*, %runtime.chanSelectState*, i32, i32, i8*)
|
||||
|
||||
attributes #0 = { nounwind }
|
||||
attributes #1 = { argmemonly nofree nosync nounwind willreturn }
|
||||
Vendored
+26
-11
@@ -1,14 +1,20 @@
|
||||
; ModuleID = 'float.go'
|
||||
source_filename = "float.go"
|
||||
target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128"
|
||||
target triple = "i686--linux"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.trackPointer(i8* nocapture readonly, i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.init(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.f32tou32(float %v, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%positive = fcmp oge float %v, 0.000000e+00
|
||||
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
|
||||
@@ -19,22 +25,26 @@ entry:
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
define internal float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden float @main.maxu32f(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret float 0x41F0000000000000
|
||||
}
|
||||
|
||||
define internal i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.maxu32tof32(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret i32 -1
|
||||
}
|
||||
|
||||
define internal { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden { i32, i32, i32, i32 } @main.inftoi32(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret { i32, i32, i32, i32 } { i32 -1, i32 0, i32 2147483647, i32 -2147483648 }
|
||||
}
|
||||
|
||||
define internal i32 @main.u32tof32tou32(i32 %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i32 @main.u32tof32tou32(i32 %v, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = uitofp i32 %v to float
|
||||
%withinmax = fcmp ole float %0, 0x41EFFFFFC0000000
|
||||
@@ -43,7 +53,8 @@ entry:
|
||||
ret i32 %1
|
||||
}
|
||||
|
||||
define internal float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden float @main.f32tou32tof32(float %v, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%positive = fcmp oge float %v, 0.000000e+00
|
||||
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
|
||||
@@ -55,7 +66,8 @@ entry:
|
||||
ret float %1
|
||||
}
|
||||
|
||||
define internal i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.f32tou8(float %v, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%positive = fcmp oge float %v, 0.000000e+00
|
||||
%withinmax = fcmp ole float %v, 2.550000e+02
|
||||
@@ -66,7 +78,8 @@ entry:
|
||||
ret i8 %0
|
||||
}
|
||||
|
||||
define internal i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8 @main.f32toi8(float %v, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%abovemin = fcmp oge float %v, -1.280000e+02
|
||||
%belowmax = fcmp ole float %v, 1.270000e+02
|
||||
@@ -78,3 +91,5 @@ entry:
|
||||
%0 = select i1 %inbounds, i8 %normal, i8 %remapped
|
||||
ret i8 %0
|
||||
}
|
||||
|
||||
attributes #0 = { nounwind }
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
func foo(callback func(int)) {
|
||||
callback(3)
|
||||
}
|
||||
|
||||
func bar() {
|
||||
foo(someFunc)
|
||||
}
|
||||
|
||||
func someFunc(int) {
|
||||
}
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
; ModuleID = 'func.go'
|
||||
source_filename = "func.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.trackPointer(i8* nocapture readonly, i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.init(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.foo(i8* %callback.context, void ()* %callback.funcptr, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp eq void ()* %callback.funcptr, null
|
||||
br i1 %0, label %fpcall.throw, label %fpcall.next
|
||||
|
||||
fpcall.throw: ; preds = %entry
|
||||
call void @runtime.nilPanic(i8* undef) #0
|
||||
unreachable
|
||||
|
||||
fpcall.next: ; preds = %entry
|
||||
%1 = bitcast void ()* %callback.funcptr to void (i32, i8*)*
|
||||
call void %1(i32 3, i8* %callback.context) #0
|
||||
ret void
|
||||
}
|
||||
|
||||
declare void @runtime.nilPanic(i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.bar(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
call void @main.foo(i8* undef, void ()* bitcast (void (i32, i8*)* @main.someFunc to void ()*), i8* undef)
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.someFunc(i32 %arg0, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
attributes #0 = { nounwind }
|
||||
Vendored
+75
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
var (
|
||||
scalar1 *byte
|
||||
scalar2 *int32
|
||||
scalar3 *int64
|
||||
scalar4 *float32
|
||||
|
||||
array1 *[3]byte
|
||||
array2 *[71]byte
|
||||
array3 *[3]*byte
|
||||
|
||||
struct1 *struct{}
|
||||
struct2 *struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
struct3 *struct {
|
||||
x *byte
|
||||
y [60]uintptr
|
||||
z *byte
|
||||
}
|
||||
struct4 *struct {
|
||||
x *byte
|
||||
y [61]uintptr
|
||||
}
|
||||
|
||||
slice1 []byte
|
||||
slice2 []*int
|
||||
slice3 [][]byte
|
||||
)
|
||||
|
||||
func newScalar() {
|
||||
scalar1 = new(byte)
|
||||
scalar2 = new(int32)
|
||||
scalar3 = new(int64)
|
||||
scalar4 = new(float32)
|
||||
}
|
||||
|
||||
func newArray() {
|
||||
array1 = new([3]byte)
|
||||
array2 = new([71]byte)
|
||||
array3 = new([3]*byte)
|
||||
}
|
||||
|
||||
func newStruct() {
|
||||
struct1 = new(struct{})
|
||||
struct2 = new(struct {
|
||||
x int
|
||||
y int
|
||||
})
|
||||
struct3 = new(struct {
|
||||
x *byte
|
||||
y [60]uintptr
|
||||
z *byte
|
||||
})
|
||||
struct4 = new(struct {
|
||||
x *byte
|
||||
y [61]uintptr
|
||||
})
|
||||
}
|
||||
|
||||
func newFuncValue() *func() {
|
||||
return new(func())
|
||||
}
|
||||
|
||||
func makeSlice() {
|
||||
slice1 = make([]byte, 5)
|
||||
slice2 = make([]*int, 5)
|
||||
slice3 = make([][]byte, 5)
|
||||
}
|
||||
|
||||
func makeInterface(v complex128) interface{} {
|
||||
return v // always stored in an allocation
|
||||
}
|
||||
Vendored
+135
@@ -0,0 +1,135 @@
|
||||
; ModuleID = 'gc.go'
|
||||
source_filename = "gc.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 }
|
||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
||||
%runtime._interface = type { i32, i8* }
|
||||
|
||||
@main.scalar1 = hidden global i8* null, align 4
|
||||
@main.scalar2 = hidden global i32* null, align 4
|
||||
@main.scalar3 = hidden global i64* null, align 4
|
||||
@main.scalar4 = hidden global float* null, align 4
|
||||
@main.array1 = hidden global [3 x i8]* null, align 4
|
||||
@main.array2 = hidden global [71 x i8]* null, align 4
|
||||
@main.array3 = hidden global [3 x i8*]* null, align 4
|
||||
@main.struct1 = hidden global {}* null, align 4
|
||||
@main.struct2 = hidden global { i32, i32 }* null, align 4
|
||||
@main.struct3 = hidden global { i8*, [60 x i32], i8* }* null, align 4
|
||||
@main.struct4 = hidden global { i8*, [61 x i32] }* null, align 4
|
||||
@main.slice1 = hidden global { i8*, i32, i32 } zeroinitializer, align 8
|
||||
@main.slice2 = hidden global { i32**, i32, i32 } zeroinitializer, align 8
|
||||
@main.slice3 = hidden global { { i8*, i32, i32 }*, i32, i32 } zeroinitializer, align 8
|
||||
@"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c" \00\00\00\00\00\00\01" }
|
||||
@"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\00\00\00\00\00\00\00\01" }
|
||||
@"reflect/types.type:basic:complex128" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:complex128", i32 0 }
|
||||
@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:complex128", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 }
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.trackPointer(i8* nocapture readonly, i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.init(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.newScalar(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%new = call i8* @runtime.alloc(i32 1, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new, i8* undef) #0
|
||||
store i8* %new, i8** @main.scalar1, align 4
|
||||
%new1 = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new1, i8* undef) #0
|
||||
store i8* %new1, i8** bitcast (i32** @main.scalar2 to i8**), align 4
|
||||
%new2 = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new2, i8* undef) #0
|
||||
store i8* %new2, i8** bitcast (i64** @main.scalar3 to i8**), align 4
|
||||
%new3 = call i8* @runtime.alloc(i32 4, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new3, i8* undef) #0
|
||||
store i8* %new3, i8** bitcast (float** @main.scalar4 to i8**), align 4
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.newArray(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%new = call i8* @runtime.alloc(i32 3, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new, i8* undef) #0
|
||||
store i8* %new, i8** bitcast ([3 x i8]** @main.array1 to i8**), align 4
|
||||
%new1 = call i8* @runtime.alloc(i32 71, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new1, i8* undef) #0
|
||||
store i8* %new1, i8** bitcast ([71 x i8]** @main.array2 to i8**), align 4
|
||||
%new2 = call i8* @runtime.alloc(i32 12, i8* nonnull inttoptr (i32 67 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new2, i8* undef) #0
|
||||
store i8* %new2, i8** bitcast ([3 x i8*]** @main.array3 to i8**), align 4
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.newStruct(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%new = call i8* @runtime.alloc(i32 0, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new, i8* undef) #0
|
||||
store i8* %new, i8** bitcast ({}** @main.struct1 to i8**), align 4
|
||||
%new1 = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new1, i8* undef) #0
|
||||
store i8* %new1, i8** bitcast ({ i32, i32 }** @main.struct2 to i8**), align 4
|
||||
%new2 = call i8* @runtime.alloc(i32 248, i8* bitcast ({ i32, [8 x i8] }* @"runtime/gc.layout:62-2000000000000001" to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new2, i8* undef) #0
|
||||
store i8* %new2, i8** bitcast ({ i8*, [60 x i32], i8* }** @main.struct3 to i8**), align 4
|
||||
%new3 = call i8* @runtime.alloc(i32 248, i8* bitcast ({ i32, [8 x i8] }* @"runtime/gc.layout:62-0001" to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %new3, i8* undef) #0
|
||||
store i8* %new3, i8** bitcast ({ i8*, [61 x i32] }** @main.struct4 to i8**), align 4
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden { i8*, void ()* }* @main.newFuncValue(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%new = call i8* @runtime.alloc(i32 8, i8* nonnull inttoptr (i32 197 to i8*), i8* undef) #0
|
||||
%0 = bitcast i8* %new to { i8*, void ()* }*
|
||||
call void @runtime.trackPointer(i8* nonnull %new, i8* undef) #0
|
||||
ret { i8*, void ()* }* %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.makeSlice(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%makeslice = call i8* @runtime.alloc(i32 5, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %makeslice, i8* undef) #0
|
||||
store i8* %makeslice, i8** getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 0), align 8
|
||||
store i32 5, i32* getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 1), align 4
|
||||
store i32 5, i32* getelementptr inbounds ({ i8*, i32, i32 }, { i8*, i32, i32 }* @main.slice1, i32 0, i32 2), align 8
|
||||
%makeslice1 = call i8* @runtime.alloc(i32 20, i8* nonnull inttoptr (i32 67 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %makeslice1, i8* undef) #0
|
||||
store i8* %makeslice1, i8** bitcast ({ i32**, i32, i32 }* @main.slice2 to i8**), align 8
|
||||
store i32 5, i32* getelementptr inbounds ({ i32**, i32, i32 }, { i32**, i32, i32 }* @main.slice2, i32 0, i32 1), align 4
|
||||
store i32 5, i32* getelementptr inbounds ({ i32**, i32, i32 }, { i32**, i32, i32 }* @main.slice2, i32 0, i32 2), align 8
|
||||
%makeslice3 = call i8* @runtime.alloc(i32 60, i8* nonnull inttoptr (i32 71 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %makeslice3, i8* undef) #0
|
||||
store i8* %makeslice3, i8** bitcast ({ { i8*, i32, i32 }*, i32, i32 }* @main.slice3 to i8**), align 8
|
||||
store i32 5, i32* getelementptr inbounds ({ { i8*, i32, i32 }*, i32, i32 }, { { i8*, i32, i32 }*, i32, i32 }* @main.slice3, i32 0, i32 1), align 4
|
||||
store i32 5, i32* getelementptr inbounds ({ { i8*, i32, i32 }*, i32, i32 }, { { i8*, i32, i32 }*, i32, i32 }* @main.slice3, i32 0, i32 2), align 8
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden %runtime._interface @main.makeInterface(double %v.r, double %v.i, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = call i8* @runtime.alloc(i32 16, i8* null, i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %0, i8* undef) #0
|
||||
%.repack = bitcast i8* %0 to double*
|
||||
store double %v.r, double* %.repack, align 8
|
||||
%.repack1 = getelementptr inbounds i8, i8* %0, i32 8
|
||||
%1 = bitcast i8* %.repack1 to double*
|
||||
store double %v.i, double* %1, align 8
|
||||
%2 = insertvalue %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:complex128" to i32), i8* undef }, i8* %0, 1
|
||||
call void @runtime.trackPointer(i8* nonnull %0, i8* undef) #0
|
||||
ret %runtime._interface %2
|
||||
}
|
||||
|
||||
attributes #0 = { nounwind }
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
// Test changes to the language introduced in Go 1.17.
|
||||
// For details, see: https://tip.golang.org/doc/go1.17#language
|
||||
// These tests should be merged into the regular slice tests once Go 1.17 is the
|
||||
// minimun Go version for TinyGo.
|
||||
|
||||
import "unsafe"
|
||||
|
||||
func Add32(p unsafe.Pointer, len int) unsafe.Pointer {
|
||||
return unsafe.Add(p, len)
|
||||
}
|
||||
|
||||
func Add64(p unsafe.Pointer, len int64) unsafe.Pointer {
|
||||
return unsafe.Add(p, len)
|
||||
}
|
||||
|
||||
func SliceToArray(s []int) *[4]int {
|
||||
return (*[4]int)(s)
|
||||
}
|
||||
|
||||
func SliceToArrayConst() *[4]int {
|
||||
s := make([]int, 6)
|
||||
return (*[4]int)(s)
|
||||
}
|
||||
|
||||
func SliceInt(ptr *int, len int) []int {
|
||||
return unsafe.Slice(ptr, len)
|
||||
}
|
||||
|
||||
func SliceUint16(ptr *byte, len uint16) []byte {
|
||||
return unsafe.Slice(ptr, len)
|
||||
}
|
||||
|
||||
func SliceUint64(ptr *int, len uint64) []int {
|
||||
return unsafe.Slice(ptr, len)
|
||||
}
|
||||
|
||||
func SliceInt64(ptr *int, len int64) []int {
|
||||
return unsafe.Slice(ptr, len)
|
||||
}
|
||||
Vendored
+159
@@ -0,0 +1,159 @@
|
||||
; ModuleID = 'go1.17.go'
|
||||
source_filename = "go1.17.go"
|
||||
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128-ni:1:10:20"
|
||||
target triple = "wasm32-unknown-wasi"
|
||||
|
||||
declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*)
|
||||
|
||||
declare void @runtime.trackPointer(i8* nocapture readonly, i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden void @main.init(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8* @main.Add32(i8* %p, i32 %len, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = getelementptr i8, i8* %p, i32 %len
|
||||
call void @runtime.trackPointer(i8* %0, i8* undef) #0
|
||||
ret i8* %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden i8* @main.Add64(i8* %p, i64 %len, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = trunc i64 %len to i32
|
||||
%1 = getelementptr i8, i8* %p, i32 %0
|
||||
call void @runtime.trackPointer(i8* %1, i8* undef) #0
|
||||
ret i8* %1
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden [4 x i32]* @main.SliceToArray(i32* %s.data, i32 %s.len, i32 %s.cap, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp ult i32 %s.len, 4
|
||||
br i1 %0, label %slicetoarray.throw, label %slicetoarray.next
|
||||
|
||||
slicetoarray.throw: ; preds = %entry
|
||||
call void @runtime.sliceToArrayPointerPanic(i8* undef) #0
|
||||
unreachable
|
||||
|
||||
slicetoarray.next: ; preds = %entry
|
||||
%1 = bitcast i32* %s.data to [4 x i32]*
|
||||
ret [4 x i32]* %1
|
||||
}
|
||||
|
||||
declare void @runtime.sliceToArrayPointerPanic(i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden [4 x i32]* @main.SliceToArrayConst(i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%makeslice = call i8* @runtime.alloc(i32 24, i8* nonnull inttoptr (i32 3 to i8*), i8* undef) #0
|
||||
call void @runtime.trackPointer(i8* nonnull %makeslice, i8* undef) #0
|
||||
br i1 false, label %slicetoarray.throw, label %slicetoarray.next
|
||||
|
||||
slicetoarray.throw: ; preds = %entry
|
||||
unreachable
|
||||
|
||||
slicetoarray.next: ; preds = %entry
|
||||
%0 = bitcast i8* %makeslice to [4 x i32]*
|
||||
ret [4 x i32]* %0
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden { i32*, i32, i32 } @main.SliceInt(i32* dereferenceable_or_null(4) %ptr, i32 %len, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp ugt i32 %len, 1073741823
|
||||
%1 = icmp eq i32* %ptr, null
|
||||
%2 = icmp ne i32 %len, 0
|
||||
%3 = and i1 %1, %2
|
||||
%4 = or i1 %3, %0
|
||||
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(i8* undef) #0
|
||||
unreachable
|
||||
|
||||
unsafe.Slice.next: ; preds = %entry
|
||||
%5 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
|
||||
%6 = insertvalue { i32*, i32, i32 } %5, i32 %len, 1
|
||||
%7 = insertvalue { i32*, i32, i32 } %6, i32 %len, 2
|
||||
%8 = bitcast i32* %ptr to i8*
|
||||
call void @runtime.trackPointer(i8* %8, i8* undef) #0
|
||||
ret { i32*, i32, i32 } %7
|
||||
}
|
||||
|
||||
declare void @runtime.unsafeSlicePanic(i8*)
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden { i8*, i32, i32 } @main.SliceUint16(i8* dereferenceable_or_null(1) %ptr, i16 %len, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp eq i8* %ptr, null
|
||||
%1 = icmp ne i16 %len, 0
|
||||
%2 = and i1 %0, %1
|
||||
br i1 %2, label %unsafe.Slice.throw, label %unsafe.Slice.next
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(i8* undef) #0
|
||||
unreachable
|
||||
|
||||
unsafe.Slice.next: ; preds = %entry
|
||||
%3 = zext i16 %len to i32
|
||||
%4 = insertvalue { i8*, i32, i32 } undef, i8* %ptr, 0
|
||||
%5 = insertvalue { i8*, i32, i32 } %4, i32 %3, 1
|
||||
%6 = insertvalue { i8*, i32, i32 } %5, i32 %3, 2
|
||||
call void @runtime.trackPointer(i8* %ptr, i8* undef) #0
|
||||
ret { i8*, i32, i32 } %6
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden { i32*, i32, i32 } @main.SliceUint64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp ugt i64 %len, 1073741823
|
||||
%1 = icmp eq i32* %ptr, null
|
||||
%2 = icmp ne i64 %len, 0
|
||||
%3 = and i1 %1, %2
|
||||
%4 = or i1 %3, %0
|
||||
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(i8* undef) #0
|
||||
unreachable
|
||||
|
||||
unsafe.Slice.next: ; preds = %entry
|
||||
%5 = trunc i64 %len to i32
|
||||
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
|
||||
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
|
||||
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
|
||||
%9 = bitcast i32* %ptr to i8*
|
||||
call void @runtime.trackPointer(i8* %9, i8* undef) #0
|
||||
ret { i32*, i32, i32 } %8
|
||||
}
|
||||
|
||||
; Function Attrs: nounwind
|
||||
define hidden { i32*, i32, i32 } @main.SliceInt64(i32* dereferenceable_or_null(4) %ptr, i64 %len, i8* %context) unnamed_addr #0 {
|
||||
entry:
|
||||
%0 = icmp ugt i64 %len, 1073741823
|
||||
%1 = icmp eq i32* %ptr, null
|
||||
%2 = icmp ne i64 %len, 0
|
||||
%3 = and i1 %1, %2
|
||||
%4 = or i1 %3, %0
|
||||
br i1 %4, label %unsafe.Slice.throw, label %unsafe.Slice.next
|
||||
|
||||
unsafe.Slice.throw: ; preds = %entry
|
||||
call void @runtime.unsafeSlicePanic(i8* undef) #0
|
||||
unreachable
|
||||
|
||||
unsafe.Slice.next: ; preds = %entry
|
||||
%5 = trunc i64 %len to i32
|
||||
%6 = insertvalue { i32*, i32, i32 } undef, i32* %ptr, 0
|
||||
%7 = insertvalue { i32*, i32, i32 } %6, i32 %5, 1
|
||||
%8 = insertvalue { i32*, i32, i32 } %7, i32 %5, 2
|
||||
%9 = bitcast i32* %ptr to i8*
|
||||
call void @runtime.trackPointer(i8* %9, i8* undef) #0
|
||||
ret { i32*, i32, i32 } %8
|
||||
}
|
||||
|
||||
attributes #0 = { nounwind }
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user