mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-01 17:47:46 +00:00
Compare commits
595 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 138befd8a2 | |||
| 3d13f9cfe1 | |||
| c4191da2a5 | |||
| 3fdd1a9249 | |||
| 869baca117 | |||
| 9c3e479432 | |||
| 6e480e189d | |||
| dcff8b6478 | |||
| 6862942c99 | |||
| abc478c294 | |||
| 66f76833ad | |||
| 24976a6974 | |||
| e76729c6f3 | |||
| 35c8707867 | |||
| 16e30c97d8 | |||
| 9841ac4acd | |||
| c299386906 | |||
| afcc9d6608 | |||
| 9f06798cb9 | |||
| ce407ddf1b | |||
| b689f14bb2 | |||
| 4141a9f431 | |||
| 52a20cf3bb | |||
| 1aac8a0cb1 | |||
| ba634c4cc7 | |||
| 681be2d4e3 | |||
| 01b917fb11 | |||
| d274e299ac | |||
| fcdaa83368 | |||
| 289e1aa197 | |||
| af02c09b56 | |||
| 801bd2a7ff | |||
| 9c8ef5c48f | |||
| ab41c79de7 | |||
| d3d4acaadf | |||
| 676a78776a | |||
| 8a54615a09 | |||
| 74fe213b15 | |||
| 2e9c3a1d8d | |||
| 5502182642 | |||
| f4b4dd8d62 | |||
| 9f5bd2c460 | |||
| d6cdf8ca28 | |||
| 6c5409bd17 | |||
| cca0eab3da | |||
| e161d5a82c | |||
| 1fb47a2670 | |||
| f1210caba8 | |||
| 5279bebf57 | |||
| 7842e6940d | |||
| 06f231468d | |||
| 3d6921b0e1 | |||
| 9612af466b | |||
| 868933e67c | |||
| f3bdebe2a6 | |||
| 5bae55d755 | |||
| 0bad2c9ff2 | |||
| 5642d72fbe | |||
| 92ed645a11 | |||
| 32a5d46c57 | |||
| d8cc48b09b | |||
| 9bd36597d6 | |||
| d85ac4b3cc | |||
| 3010466c55 | |||
| 36db75b366 | |||
| a5e2b27884 | |||
| a5cf704d83 | |||
| a90865506d | |||
| a867b56e5f | |||
| f159429152 | |||
| a848d720db | |||
| dbc438b2ae | |||
| c4d642007e | |||
| da0161d6ab | |||
| 5af4c073cd | |||
| 154c7c691b | |||
| 65caf777dd | |||
| a4d0877cf0 | |||
| 6ec868710b | |||
| 086645153e | |||
| cf930f652e | |||
| 0dcff35d3a | |||
| 75f13491b6 | |||
| 533d2c9ce0 | |||
| 2f4200a01b | |||
| 939b393325 | |||
| a5ee1ba4b3 | |||
| fb0bb69f62 | |||
| 5917b8baa2 | |||
| 30df912565 | |||
| e9d549d211 | |||
| 6ad631539d | |||
| cda5fffd98 | |||
| ce539ce583 | |||
| 43a31467d3 | |||
| ae92ea149c | |||
| 4568de556e | |||
| 7a4ccd916f | |||
| 4cc1cdf672 | |||
| bb27bbcb41 | |||
| 9c2d2b662b | |||
| 098f900363 | |||
| caf35cfc41 | |||
| abb09e869e | |||
| bb58783158 | |||
| 7abc67107d | |||
| 2540172cc5 | |||
| 0c4f0b1ebf | |||
| 15361c829e | |||
| 3f680b75f3 | |||
| 9a7e633997 | |||
| 39b1f8b6f5 | |||
| 9aa50853b8 | |||
| 9ca0e3f2d1 | |||
| 3cdc110462 | |||
| 7cc687d416 | |||
| ce57a034c3 | |||
| 30bee3afef | |||
| b1d24a72c1 | |||
| 6e24c86320 | |||
| 19a0270303 | |||
| 47410a4b54 | |||
| 0d9c46b59e | |||
| f93b28057a | |||
| 691185f5f4 | |||
| 163df7670a | |||
| 77c70d2758 | |||
| 7c4e83f5c0 | |||
| db27541b1a | |||
| 3bb994da9f | |||
| b3bd891ee0 | |||
| 387bca8e32 | |||
| d424b3d7ea | |||
| c20328472b | |||
| 171f793c1e | |||
| 3364da6f25 | |||
| e99b8a24fe | |||
| 69e1aa4878 | |||
| 7a78b2dc0e | |||
| 2b1d4ce96e | |||
| 3e40b08ba0 | |||
| ffeff55706 | |||
| 1dec9dcbc4 | |||
| ff833ef998 | |||
| 6eeebfeb5c | |||
| 6ab0106af3 | |||
| e690ff0d8c | |||
| 06564cbdb2 | |||
| 47dc76fc34 | |||
| d382f3a259 | |||
| 47dc1087e8 | |||
| c7d8223ab7 | |||
| 3eb33dff5d | |||
| bb146edb47 | |||
| ed9b97cc0d | |||
| b40f250530 | |||
| 184175378f | |||
| d8dbe5748a | |||
| c2bfe6bc8d | |||
| 9a015f4f64 | |||
| 9ad2315079 | |||
| 431e51b8a0 | |||
| 67de8b490d | |||
| bfa29f17da | |||
| 7123941df0 | |||
| 05d2f2c412 | |||
| 9a12d129ab | |||
| 0ecfe9eade | |||
| f50ad3585d | |||
| d39c7abb4d | |||
| 9e61e6fe4d | |||
| d1f90ef59c | |||
| 54602fe0c3 | |||
| c10dcd429c | |||
| b713001313 | |||
| 1096596b69 | |||
| 7b601b3e3c | |||
| ec54e7763d | |||
| 41afb77080 | |||
| 13fe668929 | |||
| 2a72262c33 | |||
| e8615d1007 | |||
| 9245337ecf | |||
| 8170f59440 | |||
| 622de53854 | |||
| 6cd9e3c348 | |||
| 33e2411b6a | |||
| fb1fc267ab | |||
| 490e377bba | |||
| fcedf0beaa | |||
| 4dadb31e18 | |||
| 5b81b835ba | |||
| a9a6d0ee63 | |||
| 19d5e05e37 | |||
| 9e599bac49 | |||
| 81e325205f | |||
| 71cbb1495e | |||
| efc824a103 | |||
| cb4f3f12e6 | |||
| d1b470f04e | |||
| 7aaa9e45a6 | |||
| 5c9930df3f | |||
| cf6b544cd9 | |||
| 097f628955 | |||
| 20a1c730a1 | |||
| 04f65f1189 | |||
| 2ce17a1892 | |||
| 0b9b293651 | |||
| f20c932bb9 | |||
| e7227df80d | |||
| b99175a436 | |||
| de3ffe0af7 | |||
| 1e47d9efac | |||
| 8a2e7bac04 | |||
| ae13db917f | |||
| 98dbbbfda6 | |||
| 475135f546 | |||
| 88fd2823df | |||
| 57a5b833b2 | |||
| c810628a20 | |||
| 51238fba50 | |||
| 7f829fe153 | |||
| 946184b8ba | |||
| 753162f4e0 | |||
| 0e6d2af028 | |||
| 0df4a7a35f | |||
| 9a17698d6a | |||
| 3ee47a9c1b | |||
| da7db81087 | |||
| 098fb5f39c | |||
| 47a975a44f | |||
| 83252448b0 | |||
| 222977a642 | |||
| 58565fa46d | |||
| ecaf9461ce | |||
| ae01904ab0 | |||
| 2194d447e9 | |||
| a21a039ac7 | |||
| a761f556ff | |||
| 29d65cb637 | |||
| 63005622ae | |||
| 510f145a3a | |||
| e5e324f93e | |||
| 4fa1fc6e72 | |||
| ccb803e35d | |||
| d1ac0138e6 | |||
| b132b5bc60 | |||
| 743254d5bc | |||
| 9d625a1ccb | |||
| b75f042c23 | |||
| 32c7f3baf9 | |||
| 1dc85ded47 | |||
| 26a0819119 | |||
| b59a46eef0 | |||
| 8a410b993b | |||
| e412a63a1c | |||
| a81face618 | |||
| 0ac216b093 | |||
| 9575fe628f | |||
| f4e8ea0d23 | |||
| 2d03f65d67 | |||
| 154d4a781f | |||
| b5ab114514 | |||
| 0a7e74045a | |||
| f05b378b89 | |||
| 888ca4ab0c | |||
| 903bebd071 | |||
| 848c3e55a9 | |||
| 3650c2c739 | |||
| d4e04e4e49 | |||
| e41e5106cc | |||
| ca03b8d442 | |||
| d1c4ed664e | |||
| fdaddf6917 | |||
| 50a677e9b7 | |||
| 6ad6f14a04 | |||
| 19e0f4709e | |||
| ae5b297d59 | |||
| ca1a282495 | |||
| 01f5c51b77 | |||
| ad6adfd8ff | |||
| 9815628930 | |||
| 05495c4282 | |||
| d606315515 | |||
| 60fdf81209 | |||
| 39433a3553 | |||
| 04d097f4ea | |||
| 4750635a20 | |||
| 59218cd784 | |||
| 079a789d49 | |||
| ca8e1b075a | |||
| 33024d4aa2 | |||
| 49df129ccd | |||
| 3088dcadec | |||
| 7ed7e6cb11 | |||
| 0b94e486c1 | |||
| 5ff76aacab | |||
| 3ee7599a09 | |||
| 43a66b39cc | |||
| 66b21b4c86 | |||
| a9568932be | |||
| f2fbd1dd7e | |||
| 21a9aa8102 | |||
| e1757e0347 | |||
| a05fc10699 | |||
| a685217743 | |||
| ad0c15080a | |||
| 5446c6927e | |||
| 53c83fa445 | |||
| 804dc8b1f9 | |||
| e1ceca1931 | |||
| 6620c4d2aa | |||
| ccc604d2e0 | |||
| dfab1aa717 | |||
| 75bcbbe6d8 | |||
| d599959711 | |||
| 7814964693 | |||
| 2fe4a9be71 | |||
| 163631df9e | |||
| 9ad96fd809 | |||
| 4a658b9082 | |||
| 34e0961a79 | |||
| 875d36cba0 | |||
| 1a6bed3305 | |||
| e0b9b1ecd1 | |||
| 149c9533e2 | |||
| a85df334e6 | |||
| 89a9c09af5 | |||
| 1451eeaf41 | |||
| e8c84d24a0 | |||
| a4f3457747 | |||
| aa3481e06a | |||
| 2136cb2f59 | |||
| acb3cfba6d | |||
| 8cfc4005d3 | |||
| 2dbe29327a | |||
| de0fbb5e2f | |||
| 9b1a19f184 | |||
| 1ad6953858 | |||
| 3e8cdb62c9 | |||
| 720a54a0fe | |||
| de45da5df4 | |||
| c9fc95f6f3 | |||
| 2721ab146f | |||
| 81c723db1a | |||
| 9a20af5b59 | |||
| 4e8e3f348f | |||
| 496452b676 | |||
| e86ca2080d | |||
| e2bf7bbb49 | |||
| 4d1d0c2d3b | |||
| 2281b6a3f5 | |||
| 4321923641 | |||
| 4e4b5955db | |||
| 169d5f17b8 | |||
| 9ed5eae6a9 | |||
| e2c55e3d26 | |||
| d3f5b51cd8 | |||
| 2396c22658 | |||
| d61d5d7ab1 | |||
| c5a896771d | |||
| 0c880ec44c | |||
| 64d51b215f | |||
| f103e910d7 | |||
| 40afeea569 | |||
| 97122972fb | |||
| 72064e12db | |||
| 3c31a3110f | |||
| 0e73790d67 | |||
| bcbc241d81 | |||
| 5e2a8024a3 | |||
| b92fad8da6 | |||
| 5c8d4e54d6 | |||
| fed433c046 | |||
| 734613c20e | |||
| 1570adac1c | |||
| c72f9eb08c | |||
| 19c7965fc5 | |||
| c248418dbe | |||
| 4ca2d3f0cf | |||
| 35015a7918 | |||
| bde73fc214 | |||
| ab2a81cc52 | |||
| 2a98433c8e | |||
| 4918395f88 | |||
| 67ac4fdd8e | |||
| e69131c0d3 | |||
| 9f4459cee1 | |||
| 3c55689566 | |||
| 95f509b109 | |||
| dda576e80b | |||
| da505a6b17 | |||
| 424d775bf4 | |||
| 726d735ad3 | |||
| b9fd6cee6f | |||
| 76fb3bd177 | |||
| 29ca1147f5 | |||
| 473644d918 | |||
| 38fc340802 | |||
| b5f028e1f7 | |||
| aa40ddc48b | |||
| 6bcb40fe01 | |||
| e907db1481 | |||
| 6b8940421e | |||
| 2c71f08922 | |||
| 7e64bc8f77 | |||
| 1461563e3f | |||
| acdaa72365 | |||
| 01f5c1d455 | |||
| 00f3a65903 | |||
| b4815192a6 | |||
| c54e1cc955 | |||
| 7801921cc0 | |||
| afc6bd5cdd | |||
| ae2cbbf851 | |||
| ccd79ee289 | |||
| 171d0fe123 | |||
| 5ed0f67e1c | |||
| 8ce3cfad40 | |||
| 904fa852f6 | |||
| d0b2585e82 | |||
| 23e88bfb15 | |||
| 6389e45d99 | |||
| 9342e73ae1 | |||
| fc4857e98c | |||
| f66492a338 | |||
| 445fd37bef | |||
| a9ba6ebad9 | |||
| 565ff99c31 | |||
| ceeba528e7 | |||
| 16c2d84c49 | |||
| f00bb63330 | |||
| efdb2e852e | |||
| bb540df2d1 | |||
| e181199305 | |||
| 7b2377586f | |||
| 471cb4cfd7 | |||
| 5706b062e9 | |||
| 4347496623 | |||
| 9c78f7039d | |||
| 9890c760cf | |||
| 0a8bfc57ef | |||
| 5674c35e14 | |||
| 0f9038ad2e | |||
| 343bb42644 | |||
| bb5f7534e5 | |||
| 3862d6e8a2 | |||
| 5ef8c89937 | |||
| 2078b042a4 | |||
| 9e453a5a29 | |||
| a08d3aa1dd | |||
| 0afd42c439 | |||
| 584e94ce2f | |||
| 5308e8903e | |||
| e077e35386 | |||
| 4a6fba7e3a | |||
| 012c4a02c9 | |||
| dd0fb1dd9a | |||
| 639ec1e6ee | |||
| 8333c171f4 | |||
| b8f5627c9f | |||
| 9f8715c143 | |||
| cbaa58a2d9 | |||
| 46345aade6 | |||
| f06d7d1bd6 | |||
| 407149e323 | |||
| 5d539df216 | |||
| ccf8b378b3 | |||
| ae16b2c922 | |||
| 6647c43a7b | |||
| 6e86daa95e | |||
| 62e78c0a26 | |||
| 57320c0922 | |||
| 03fa9dd9b7 | |||
| 06797b6d1a | |||
| 0596b3c003 | |||
| cc4a4c755f | |||
| 2501602b4f | |||
| 5cc130bb6e | |||
| 91d1a23b14 | |||
| eaa54bc7e3 | |||
| c01f81144e | |||
| 67c242173c | |||
| f8876ea245 | |||
| bbfa601d27 | |||
| 19f8874764 | |||
| 85854cd58b | |||
| 980068543a | |||
| c4fd19be99 | |||
| 8ef921e028 | |||
| 315b028317 | |||
| c8b5042870 | |||
| ad992e2456 | |||
| c1521fe12e | |||
| 405ec2a563 | |||
| 19bf8acde0 | |||
| d752e66be5 | |||
| d46934d1f1 | |||
| 2d9f3605b9 | |||
| ce84f77c8d | |||
| fc0ac9af8e | |||
| bee5a67097 | |||
| a1ba71ce99 | |||
| 6dafb6c65e | |||
| b8d20535ba | |||
| 7733666fa8 | |||
| 349ecf1736 | |||
| 840acdd316 | |||
| b5e29bf0c1 | |||
| 8c86eae924 | |||
| 3b1759f463 | |||
| b4dddfe439 | |||
| ac73430502 | |||
| 7446413dc9 | |||
| 83426edcdc | |||
| 26aba72729 | |||
| 25fcf3e18e | |||
| 5133604b51 | |||
| 04cec56141 | |||
| f316ebc23b | |||
| 9ec426e25e | |||
| 854092c7bc | |||
| c61c5e5799 | |||
| 046efdd93a | |||
| 9d3de55229 | |||
| 78bd7e094f | |||
| 2f88c7aab4 | |||
| 599670cef6 | |||
| 9222bda9c6 | |||
| 213a6240a1 | |||
| add014f21b | |||
| c5cb2cec9b | |||
| b6314fa6ab | |||
| 945ff4d160 | |||
| fa861085c0 | |||
| a02d4e0b77 | |||
| 9f597bbbc3 | |||
| c03e3616a2 | |||
| 0312f12696 | |||
| fbb8efd24c | |||
| a8da601672 | |||
| 9cef23c318 | |||
| 0759b70c50 | |||
| 5d869f6042 | |||
| 5089d1a5a7 | |||
| 66afcb3b39 | |||
| 5bace979ea | |||
| 63cfb09e9e | |||
| 6a50f25a48 | |||
| 2521cacb51 | |||
| 7ac531906b | |||
| 982b2d06ab | |||
| ad8996c4ee | |||
| 571a412266 | |||
| 79dae62c78 | |||
| 1a7369af6e | |||
| 928a970782 | |||
| 0c73d56861 | |||
| c8a4994feb | |||
| 3aaa029c70 | |||
| aeb7539f67 | |||
| 978a0cd9b0 | |||
| 6896b0014b | |||
| 4dfc289ae5 | |||
| 53688c86c8 | |||
| 100cbad65e | |||
| b3d3debd4c | |||
| e83d1a02b6 | |||
| 1a2f051bee | |||
| 2ca0f0d0c6 | |||
| 4da137d7c8 | |||
| c622cbac39 | |||
| 67229af879 | |||
| c0b8716d7f | |||
| 0655086048 | |||
| bf57ae01fa | |||
| 3f74e3c41f | |||
| 0c0af6d3fe | |||
| 765440b8ec | |||
| b07b13ca1b | |||
| 19c6ac01df | |||
| 9e79b41bb4 | |||
| b4ee57db26 | |||
| 6f0c49ad7a | |||
| e29379ce3a | |||
| f0fd24e7ba | |||
| bdef52ad62 | |||
| fd1785ed2d | |||
| 94ec082f6c | |||
| 630c498efa | |||
| d11abb33fe | |||
| e3ae57090f | |||
| a0cdd6b4ed | |||
| c721cae48b | |||
| 2138fd7854 |
+124
-65
@@ -14,20 +14,21 @@ commands:
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
echo 'deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch<<parameters.llvm>> main' | sudo tee /etc/apt/sources.list.d/llvm.list
|
||||
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>> \
|
||||
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:
|
||||
@@ -37,59 +38,91 @@ commands:
|
||||
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:
|
||||
type: string
|
||||
steps:
|
||||
- run:
|
||||
name: "Install Xtensa toolchain"
|
||||
command: |
|
||||
curl -L https://github.com/espressif/crosstool-NG/releases/download/esp-2020r2/xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz -o xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.tar.gz
|
||||
sudo tar -C /usr/local -xf xtensa-esp32-elf-gcc8_2_0-esp-2020r2-<<parameters.variant>>.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-<<parameters.variant>>.tar.gz
|
||||
llvm-source-linux:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-source-9-v0
|
||||
- llvm-source-11-v1
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-9-v0
|
||||
key: llvm-source-11-v1
|
||||
paths:
|
||||
- llvm-project
|
||||
build-wasi-libc:
|
||||
steps:
|
||||
- restore_cache:
|
||||
keys:
|
||||
- wasi-libc-sysroot-v0
|
||||
- wasi-libc-sysroot-v4
|
||||
- run:
|
||||
name: "Build wasi-libc"
|
||||
command: make wasi-libc CLANG=$PWD/llvm-build/bin/clang LLVM_AR=$PWD/llvm-build/bin/llvm-ar LLVM_NM=$PWD/llvm-build/bin/llvm-nm
|
||||
command: make wasi-libc
|
||||
- save_cache:
|
||||
key: wasi-libc-sysroot-v0
|
||||
key: wasi-libc-sysroot-v4
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
test-linux:
|
||||
parameters:
|
||||
llvm:
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- submodules
|
||||
- apt-dependencies:
|
||||
llvm: "-9"
|
||||
llvm: "<<parameters.llvm>>"
|
||||
- install-node
|
||||
- install-chrome
|
||||
- install-wasmtime
|
||||
- restore_cache:
|
||||
keys:
|
||||
- go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_PREVIOUS_BUILD_NUM }}
|
||||
- go-cache-v2-{{ checksum "go.mod" }}
|
||||
- llvm-source-linux
|
||||
- run: go install .
|
||||
- run: go install -tags=llvm<<parameters.llvm>> .
|
||||
- restore_cache:
|
||||
keys:
|
||||
- wasi-libc-sysroot-systemclang-v0
|
||||
- wasi-libc-sysroot-systemclang-v3
|
||||
- run: make wasi-libc
|
||||
- save_cache:
|
||||
key: wasi-libc-sysroot-systemclang-v0
|
||||
key: wasi-libc-sysroot-systemclang-v3
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
- run: go test -v ./cgo ./compileopts ./interp ./transform .
|
||||
- run: go test -v -tags=llvm<<parameters.llvm>> ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest
|
||||
- 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
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run: make fmt-check
|
||||
assert-test-linux:
|
||||
@@ -99,9 +132,9 @@ commands:
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
binutils-arm-none-eabi \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
@@ -109,7 +142,11 @@ commands:
|
||||
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 }}
|
||||
@@ -117,17 +154,14 @@ commands:
|
||||
- llvm-source-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-9-linux-v0-assert
|
||||
- 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 clang ninja-build
|
||||
# make build faster
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
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
|
||||
@@ -135,7 +169,7 @@ commands:
|
||||
make ASSERT=1 llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-9-linux-v0-assert
|
||||
key: llvm-build-11-linux-v2-assert
|
||||
paths:
|
||||
llvm-build
|
||||
- run: make ASSERT=1
|
||||
@@ -147,7 +181,6 @@ commands:
|
||||
key: go-cache-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run: make gen-device -j4
|
||||
- run: make smoketest TINYGO=build/tinygo
|
||||
@@ -158,9 +191,9 @@ commands:
|
||||
- run:
|
||||
name: "Install apt dependencies"
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install \
|
||||
gcc-arm-linux-gnueabihf \
|
||||
binutils-arm-none-eabi \
|
||||
libc6-dev-armel-cross \
|
||||
gcc-aarch64-linux-gnu \
|
||||
libc6-dev-arm64-cross \
|
||||
@@ -168,7 +201,11 @@ commands:
|
||||
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 }}
|
||||
@@ -176,17 +213,14 @@ commands:
|
||||
- llvm-source-linux
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-9-linux-v0
|
||||
- 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 clang ninja-build
|
||||
# make build faster
|
||||
export CC=clang
|
||||
export CXX=clang++
|
||||
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
|
||||
@@ -194,25 +228,32 @@ commands:
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-9-linux-v0
|
||||
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 -j3
|
||||
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
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
- run:
|
||||
name: "Extract release tarball"
|
||||
@@ -229,27 +270,29 @@ commands:
|
||||
- run:
|
||||
name: "Install dependencies"
|
||||
command: |
|
||||
curl https://dl.google.com/go/go1.13.darwin-amd64.tar.gz -o go1.13.darwin-amd64.tar.gz
|
||||
sudo tar -C /usr/local -xzf go1.13.darwin-amd64.tar.gz
|
||||
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-9-macos-v0
|
||||
- llvm-source-11-macos-v1
|
||||
- run:
|
||||
name: "Fetch LLVM source"
|
||||
command: make llvm-source
|
||||
- save_cache:
|
||||
key: llvm-source-9-macos-v0
|
||||
key: llvm-source-11-macos-v1
|
||||
paths:
|
||||
- llvm-project
|
||||
- restore_cache:
|
||||
keys:
|
||||
- llvm-build-9-macos-v0
|
||||
- llvm-build-11-macos-v2
|
||||
- run:
|
||||
name: "Build LLVM"
|
||||
command: |
|
||||
@@ -261,17 +304,17 @@ commands:
|
||||
make llvm-build
|
||||
fi
|
||||
- save_cache:
|
||||
key: llvm-build-9-macos-v0
|
||||
key: llvm-build-11-macos-v2
|
||||
paths:
|
||||
llvm-build
|
||||
- restore_cache:
|
||||
keys:
|
||||
- wasi-libc-sysroot-macos-v0
|
||||
- wasi-libc-sysroot-macos-v3
|
||||
- run:
|
||||
name: "Build wasi-libc"
|
||||
command: make wasi-libc CLANG=$PWD/llvm-build/bin/clang LLVM_AR=$PWD/llvm-build/bin/llvm-ar LLVM_NM=$PWD/llvm-build/bin/llvm-nm
|
||||
command: make wasi-libc
|
||||
- save_cache:
|
||||
key: wasi-libc-sysroot-macos-v0
|
||||
key: wasi-libc-sysroot-macos-v3
|
||||
paths:
|
||||
- lib/wasi-libc/sysroot
|
||||
- run:
|
||||
@@ -291,44 +334,58 @@ commands:
|
||||
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:
|
||||
name: "Download SiFive GNU toolchain"
|
||||
command: |
|
||||
curl -O https://static.dev.sifive.com/dev-tools/riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
|
||||
sudo tar -C /usr/local --strip-components=1 -xf riscv64-unknown-elf-gcc-8.2.0-2019.05.3-x86_64-apple-darwin.tar.gz
|
||||
- run: make smoketest AVR=0
|
||||
- save_cache:
|
||||
key: go-cache-macos-v2-{{ checksum "go.mod" }}-{{ .Environment.CIRCLE_BUILD_NUM }}
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
- ~/.cache/tinygo
|
||||
- /go/pkg/mod
|
||||
|
||||
|
||||
jobs:
|
||||
test-llvm9-go111:
|
||||
test-llvm10-go111:
|
||||
docker:
|
||||
- image: circleci/golang:1.11-stretch
|
||||
- image: circleci/golang:1.11-buster
|
||||
steps:
|
||||
- test-linux
|
||||
test-llvm9-go112:
|
||||
- test-linux:
|
||||
llvm: "10"
|
||||
test-llvm10-go112:
|
||||
docker:
|
||||
- image: circleci/golang:1.12-stretch
|
||||
- image: circleci/golang:1.12-buster
|
||||
steps:
|
||||
- test-linux
|
||||
test-llvm9-go113:
|
||||
- test-linux:
|
||||
llvm: "10"
|
||||
test-llvm10-go113:
|
||||
docker:
|
||||
- image: circleci/golang:1.13-stretch
|
||||
- image: circleci/golang:1.13-buster
|
||||
steps:
|
||||
- test-linux
|
||||
- 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:
|
||||
docker:
|
||||
- image: circleci/golang:1.16-buster
|
||||
steps:
|
||||
- test-linux:
|
||||
llvm: "11"
|
||||
assert-test-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.13-stretch
|
||||
- image: circleci/golang:1.14-stretch
|
||||
steps:
|
||||
- assert-test-linux
|
||||
build-linux:
|
||||
docker:
|
||||
- image: circleci/golang:1.13-stretch
|
||||
- image: circleci/golang:1.14-stretch
|
||||
steps:
|
||||
- build-linux
|
||||
build-macos:
|
||||
@@ -339,13 +396,15 @@ jobs:
|
||||
|
||||
|
||||
|
||||
|
||||
workflows:
|
||||
test-all:
|
||||
jobs:
|
||||
- test-llvm9-go111
|
||||
- test-llvm9-go112
|
||||
- test-llvm9-go113
|
||||
- test-llvm10-go111
|
||||
- test-llvm10-go112
|
||||
- test-llvm10-go113
|
||||
- test-llvm10-go114
|
||||
- test-llvm11-go115
|
||||
- test-llvm11-go116
|
||||
- build-linux
|
||||
- build-macos
|
||||
- assert-test-linux
|
||||
|
||||
@@ -3,14 +3,19 @@ docs/_build
|
||||
src/device/avr/*.go
|
||||
src/device/avr/*.ld
|
||||
src/device/avr/*.s
|
||||
src/device/esp/*.go
|
||||
src/device/nrf/*.go
|
||||
src/device/nrf/*.s
|
||||
src/device/nxp/*.go
|
||||
src/device/nxp/*.s
|
||||
src/device/sam/*.go
|
||||
src/device/sam/*.s
|
||||
src/device/sifive/*.go
|
||||
src/device/sifive/*.s
|
||||
src/device/stm32/*.go
|
||||
src/device/stm32/*.s
|
||||
src/device/kendryte/*.go
|
||||
src/device/kendryte/*.s
|
||||
vendor
|
||||
llvm-build
|
||||
llvm-project
|
||||
|
||||
@@ -17,3 +17,9 @@
|
||||
[submodule "lib/wasi-libc"]
|
||||
path = lib/wasi-libc
|
||||
url = https://github.com/CraneStation/wasi-libc
|
||||
[submodule "lib/picolibc"]
|
||||
path = lib/picolibc
|
||||
url = https://github.com/keith-packard/picolibc.git
|
||||
[submodule "lib/stm32-svd"]
|
||||
path = lib/stm32-svd
|
||||
url = https://github.com/tinygo-org/stm32-svd
|
||||
|
||||
+12
@@ -10,6 +10,14 @@ This guide describes how to statically link TinyGo against LLVM, libclang and
|
||||
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)
|
||||
|
||||
## Dependencies
|
||||
|
||||
LLVM, Clang and LLD are quite light on dependencies, requiring only standard
|
||||
@@ -84,6 +92,10 @@ Now that we have a working static build, it's time to make a release tarball:
|
||||
|
||||
make release
|
||||
|
||||
If you did not clone the repository with the `--recursive` option, you will get errors until you initialize the project submodules:
|
||||
|
||||
git submodule update --init
|
||||
|
||||
The release tarball is stored in build/release.tar.gz, and can be extracted with
|
||||
the following command (for example in ~/lib):
|
||||
|
||||
|
||||
+407
@@ -1,3 +1,410 @@
|
||||
0.17.0
|
||||
|
||||
---
|
||||
* **command line**
|
||||
- switch to LLVM 11 for static builds
|
||||
- support gdb debugging with AVR
|
||||
- add support for additional openocd commands
|
||||
- add `-x` flag to print commands
|
||||
- use LLVM 11 by default when linking LLVM dynamically
|
||||
- update go-llvm to use LLVM 11 on macOS
|
||||
- bump go.bug.st/serial to version 1.1.2
|
||||
- do not build LLVM with libxml to work around a bugo on macOS
|
||||
- add support for Go 1.16
|
||||
- support gdb daemonization on Windows
|
||||
- remove support for LLVM 9, to fix CI
|
||||
- kill OpenOCD if it does not exit with a regular quit signal
|
||||
- support `-ocd-output` on Windows
|
||||
* **compiler**
|
||||
- `builder`: parallelize most of the build
|
||||
- `builder`: remove unused cacheKey parameter
|
||||
- `builder`: add -mcpu flag while building libraries
|
||||
- `builder`: wait for running jobs to finish
|
||||
- `cgo`: add support for variadic functions
|
||||
- `compiler`: fix undefined behavior in wordpack
|
||||
- `compiler`: fix incorrect "exported function" panic
|
||||
- `compiler`: fix non-int integer constants (fixing a crash)
|
||||
- `compiler`: refactor and add tests
|
||||
- `compiler`: emit a nil check when slicing an array pointer
|
||||
- `compiler`: saturate float-to-int conversions
|
||||
- `compiler`: test float to int conversions and fix upper-bound calculation
|
||||
- `compiler`: support all kinds of deferred builtins
|
||||
- `compiler`: remove ir package
|
||||
- `compiler`: remove unnecessary main.main call workaround
|
||||
- `compiler`: move the setting of attributes to getFunction
|
||||
- `compiler`: create runtime types lazily when needed
|
||||
- `compiler`: move settings to a separate Config struct
|
||||
- `compiler`: work around an ARM backend bug in LLVM
|
||||
- `interp`: rewrite entire package
|
||||
- `interp`: fix alignment of untyped globals
|
||||
- `loader`: use name "main" for the main package
|
||||
- `loader`: support imports from vendor directories
|
||||
- `stacksize`: add support for DW_CFA_offset_extended
|
||||
- `transform`: show better error message in coroutines lowering
|
||||
* **standard library**
|
||||
- `machine`: accept configuration struct for ADC parameters
|
||||
- `machine`: make I2C.Configure signature consistent
|
||||
- `reflect`: implement PtrTo
|
||||
- `runtime`: refactor to simplify stack switching
|
||||
- `runtime`: put metadata at the top end of the heap
|
||||
* **targets**
|
||||
- `atsam`: add a length check to findPinPadMapping
|
||||
- `atsam`: improve USBCDC
|
||||
- `atsam`: avoid infinite loop when USBCDC is disconnected
|
||||
- `avr`: add SPI support for Atmega based chips
|
||||
- `avr`: use Clang for compiling C and assembly files
|
||||
- `esp32`: implement task based scheduler
|
||||
- `esp32`: enable the FPU
|
||||
- `esp8266`: implement task based scheduler
|
||||
- `esp`: add compiler-rt library
|
||||
- `esp`: add picolibc
|
||||
- `nrf`: refactor code a bit to reduce duplication
|
||||
- `nrf`: use SPIM peripheral instead of the legacy SPI peripheral
|
||||
- `nrf`: update nrfx submodule to latest commit
|
||||
- `nrf52840`: ensure that USB CDC interface is only initialized once
|
||||
- `nrf52840`: improve USBCDC
|
||||
- `stm32`: use stm32-rs SVDs which are of much higher quality
|
||||
- `stm32`: harmonization of UART logic
|
||||
- `stm32`: replace I2C addressable interface with simpler type
|
||||
- `stm32`: fix i2c and add stm32f407 i2c
|
||||
- `stm32`: revert change that adds support for channels in interrupts
|
||||
- `wasm`: implement a growable heap
|
||||
- `wasm`: fix typo in wasm_exec.js, syscall/js.valueLoadString()
|
||||
- `wasm`: Namespaced Wasm Imports so they don't conflict across modules, or reserved LLVM IR
|
||||
- `wasi`: support env variables based on libc
|
||||
- `wasi`: specify wasi-libc in a different way, to improve error message
|
||||
* **boards**
|
||||
- `matrixportal-m4`: add support for board Adafruit Matrix Portal M4
|
||||
- `mkr1000`: add this board
|
||||
- `nucleo-f722ze`: add this board
|
||||
- `clue`: correct volume name and add alias for release version of Adafruit Clue board
|
||||
- `p1am-100`: add support for the P1AM-100 (similar to Arduino MKR)
|
||||
- `microbit-v2`: add initial support based on work done by @alankrantas thank you!
|
||||
- `lgt92`: support for STM32L0 MCUs and Dragino LGT92 device
|
||||
- `nicenano`: nice!nano board support
|
||||
- `circuitplay-bluefruit`: correct internal I2C pin mapping
|
||||
- `clue`: correct for lack of low frequency crystal
|
||||
- `digispark`: split off attiny85 target
|
||||
- `nucleo-l552ze`: implementation with CLOCK, LED, and UART
|
||||
- `nrf52840-mdk-usb-dongle`: add this board
|
||||
|
||||
0.16.0
|
||||
---
|
||||
|
||||
* **command-line**
|
||||
- add initial support for LLVM 11
|
||||
- make lib64 clang include path check more robust
|
||||
- `build`: improve support for GOARCH=386 and add tests
|
||||
- `gdb`: add support for qemu-user targets
|
||||
- `test`: support non-host tests
|
||||
- `test`: add support for -c and -o flags
|
||||
- `test`: implement some benchmark stubs
|
||||
* **compiler**
|
||||
- `builder`: improve detection of clang on Fedora
|
||||
- `compiler`: fix floating point comparison bugs
|
||||
- `compiler`: implement negate for complex numbers
|
||||
- `loader`: fix linkname in test binaries
|
||||
- `transform`: add missing return pointer restore for regular coroutine tail
|
||||
calls
|
||||
* **standard library**
|
||||
- `machine`: switch default frequency to 4MHz
|
||||
- `machine`: clarify caller's responsibility in `SetInterrupt`
|
||||
- `os`: add `LookupEnv()` stub
|
||||
- `reflect`: implement `Swapper`
|
||||
- `runtime`: fix UTF-8 decoding
|
||||
- `runtime`: gc: use raw stack access whenever possible
|
||||
- `runtime`: use dedicated printfloat32
|
||||
- `runtime`: allow ranging over a nil map
|
||||
- `runtime`: avoid device/nxp dependency in HardFault handler
|
||||
- `testing`: implement dummy Helper method
|
||||
- `testing`: add Run method
|
||||
* **targets**
|
||||
- `arm64`: add support for SVCall intrinsic
|
||||
- `atsamd51`: avoid panic when configuring SPI with SDI=NoPin
|
||||
- `avr`: properly support the `.rodata` section
|
||||
- `esp8266`: implement `Pin.Get` function
|
||||
- `nintendoswitch`: fix crash when printing long lines (> 120)
|
||||
- `nintendoswitch`: add env parser and removed unused stuff
|
||||
- `nrf`: add I2C error checking
|
||||
- `nrf`: give more flexibility in picking SPI speeds
|
||||
- `nrf`: fix nrf52832 flash size
|
||||
- `stm32f103`: support wakeups from interrupts
|
||||
- `stm32f405`: add SPI support
|
||||
- `stm32f405`: add I2C support
|
||||
- `wasi`: add support for this target
|
||||
- `wasi`: use 'generic' ABI by default
|
||||
- `wasi`: remove --no-threads flag from wasm-ld
|
||||
- `wasm`: add instanceof support for WebAssembly
|
||||
- `wasm`: use fixed length buffer for putchar
|
||||
* **boards**
|
||||
- `d1mini`: add this ESP8266 based board
|
||||
- `esp32`: use board definitions instead of chip names
|
||||
- `qtpy`: add board definition for Adafruit QTPy
|
||||
- `teensy40`: add this board
|
||||
|
||||
0.15.0
|
||||
---
|
||||
|
||||
* **command-line**
|
||||
- add cached GOROOT to info subcommand
|
||||
- embed git-hash in tinygo-dev executable
|
||||
- implement tinygo targets to list usable targets
|
||||
- use simpler file copy instead of file renaming to avoid issues on nrf52840 UF2 bootloaders
|
||||
- use ToSlash() to specify program path
|
||||
- support flashing esp32/esp8266 directly from tinygo
|
||||
- when flashing call PortReset only on other than openocd
|
||||
* **compiler**
|
||||
- `compileopts`: add support for custom binary formats
|
||||
- `compiler`: improve display of goroutine wrappers
|
||||
- `interp`: don't panic in the Store method
|
||||
- `interp`: replace some panics with error messages
|
||||
- `interp`: show error line in first line of the traceback
|
||||
- `loader`: be more robust when creating the cached GOROOT
|
||||
- `loader`: rewrite/refactor much of the code to use go list directly
|
||||
- `loader`: use ioutil.TempDir to create a temporary directory
|
||||
- `stacksize`: deal with DW_CFA_advance_loc1
|
||||
* **standard library**
|
||||
- `runtime`: use waitForEvents when appropriate
|
||||
* **wasm**
|
||||
- `wasm`: Remove --no-threads from wasm-ld calls.
|
||||
- `wasm`: update wasi-libc dependency
|
||||
* **targets**
|
||||
- `arduino-mega2560`: fix flashing on Windows
|
||||
- `arm`: automatically determine stack sizes
|
||||
- `arm64`: make dynamic loader structs and constants private
|
||||
- `avr`: configure emulator in board files
|
||||
- `cortexm`: fix stack size calculation with interrupts
|
||||
- `flash`: add openocd settings to atsamd21 / atsamd51
|
||||
- `flash`: add openocd settings to nrf5
|
||||
- `microbit`: reelboard: flash using OpenOCD when needed
|
||||
- `nintendoswitch`: Add dynamic loader for runtime loading PIE sections
|
||||
- `nintendoswitch`: fix import cycle on dynamic_arm64.go
|
||||
- `nintendoswitch`: Fix invalid memory read / write in print calls
|
||||
- `nintendoswitch`: simplified assembly code
|
||||
- `nintendoswitch`: support outputting .nro files directly
|
||||
* **boards**
|
||||
- `arduino-zero`: Adding support for the Arduino Zero (#1365)
|
||||
- `atsamd2x`: fix BAUD value
|
||||
- `atsamd5x`: fix BAUD value
|
||||
- `bluepill`: Enable stm32's USART2 for the board and map it to UART1 tinygo's device
|
||||
- `device/atsamd51x`: add all remaining bitfield values for PCHCTRLm Mapping
|
||||
- `esp32`: add libgcc ROM functions to linker script
|
||||
- `esp32`: add SPI support
|
||||
- `esp32`: add support for basic GPIO
|
||||
- `esp32`: add support for the Espressif ESP32 chip
|
||||
- `esp32`: configure the I/O matrix for GPIO pins
|
||||
- `esp32`: export machine.PortMask* for bitbanging implementations
|
||||
- `esp8266`: add support for this chip
|
||||
- `machine/atsamd51x,runtime/atsamd51x`: fixes needed for full support for all PWM pins. Also adds some useful constants to clarify peripheral clock usage
|
||||
- `machine/itsybitsy-nrf52840`: add support for Adafruit Itsybitsy nrf52840 (#1243)
|
||||
- `machine/stm32f4`: refactor common code and add new build tag stm32f4 (#1332)
|
||||
- `nrf`: add SoftDevice support for the Circuit Playground Bluefruit
|
||||
- `nrf`: call sd_app_evt_wait when the SoftDevice is enabled
|
||||
- `nrf52840`: add build tags for SoftDevice support
|
||||
- `nrf52840`: use higher priority for USB-CDC code
|
||||
- `runtime/atsamd51x`: use PCHCTRL_GCLK_SERCOMX_SLOW for setting clocks on all SERCOM ports
|
||||
- `stm32f405`: add basic UART handler
|
||||
- `stm32f405`: add STM32F405 machine/runtime, and new board/target feather-stm32f405
|
||||
* **build**
|
||||
- `all`: run test binaries in the correct directory
|
||||
- `build`: Fix arch release job
|
||||
- `ci`: run `tinygo test` for known-working packages
|
||||
- `ci`: set git-fetch-depth to 1
|
||||
- `docker`: fix the problem with the wasm build (#1357)
|
||||
- `Makefile`: check whether submodules have been downloaded in some common cases
|
||||
* **docs**
|
||||
- add ESP32, ESP8266, and Adafruit Feather STM32F405 to list of supported boards
|
||||
|
||||
0.14.1
|
||||
---
|
||||
* **command-line**
|
||||
- support for Go 1.15
|
||||
* **compiler**
|
||||
- loader: work around Windows symlink limitation
|
||||
|
||||
0.14.0
|
||||
---
|
||||
* **command-line**
|
||||
- fix `getDefaultPort()` on non-English Windows locales
|
||||
- compileopts: improve error reporting of unsupported flags
|
||||
- fix test subcommand
|
||||
- use auto-retry to locate MSD for UF2 and HEX flashing
|
||||
- fix touchSerialPortAt1200bps on Windows
|
||||
- support package names with backslashes on Windows
|
||||
* **compiler**
|
||||
- fix a few crashes due to named types
|
||||
- add support for atomic operations
|
||||
- move the channel blocked list onto the stack
|
||||
- fix -gc=none
|
||||
- fix named string to `[]byte` slice conversion
|
||||
- implement func value and builtin defers
|
||||
- add proper parameter names to runtime.initAll, to fix a panic
|
||||
- builder: fix picolibc include path
|
||||
- builder: use newer version of gohex
|
||||
- builder: try to determine stack size information at compile time
|
||||
- builder: remove -opt=0
|
||||
- interp: fix sync/atomic.Value load/store methods
|
||||
- loader: add Go module support
|
||||
- transform: fix debug information in func lowering pass
|
||||
- transform: do not special-case zero or one implementations of a method call
|
||||
- transform: introduce check for method calls on nil interfaces
|
||||
- transform: gc: track 0-index GEPs to fix miscompilation
|
||||
* **cgo**
|
||||
- Add LDFlags support
|
||||
* **standard library**
|
||||
- extend stdlib to allow import of more packages
|
||||
- replace master/slave terminology with appropriate alternatives (MOSI->SDO
|
||||
etc)
|
||||
- `internal/bytealg`: reimplement bytealg in pure Go
|
||||
- `internal/task`: fix nil panic in (*internal/task.Stack).Pop
|
||||
- `os`: add Args and stub it with mock data
|
||||
- `os`: implement virtual filesystem support
|
||||
- `reflect`: add Cap and Len support for map and chan
|
||||
- `runtime`: fix return address in scheduler on RISC-V
|
||||
- `runtime`: avoid recursion in printuint64 function
|
||||
- `runtime`: replace ReadRegister with AsmFull inline assembly
|
||||
- `runtime`: fix compilation errors when using gc.extalloc
|
||||
- `runtime`: add cap and len support for chans
|
||||
- `runtime`: refactor time handling (improving accuracy)
|
||||
- `runtime`: make channels work in interrupts
|
||||
- `runtime/interrupt`: add cross-chip disable/restore interrupt support
|
||||
- `sync`: implement `sync.Cond`
|
||||
- `sync`: add WaitGroup
|
||||
* **targets**
|
||||
- `arm`: allow nesting in DisableInterrupts and EnableInterrupts
|
||||
- `arm`: make FPU configuraton consistent
|
||||
- `arm`: do not mask fault handlers in critical sections
|
||||
- `atmega2560`: fix pin mapping for pins D2, D5 and the L port
|
||||
- `atsamd`: return an error when an incorrect PWM pin is used
|
||||
- `atsamd`: add support for pin change interrupts
|
||||
- `atsamd`: add DAC support
|
||||
- `atsamd21`: add more ADC pins
|
||||
- `atsamd51`: fix ROM / RAM size on atsamd51j20
|
||||
- `atsamd51`: add more pins
|
||||
- `atsamd51`: add more ADC pins
|
||||
- `atsamd51`: add pin change interrupt settings
|
||||
- `atsamd51`: extend pinPadMapping
|
||||
- `arduino-nano33`: use (U)SB flag to ensure that device can be found when
|
||||
not on default port
|
||||
- `arduino-nano33`: remove (d)ebug flag to reduce console noise when flashing
|
||||
- `avr`: use standard pin numbering
|
||||
- `avr`: unify GPIO pin/port code
|
||||
- `avr`: add support for PinInputPullup
|
||||
- `avr`: work around codegen bug in LLVM 10
|
||||
- `avr`: fix target triple
|
||||
- `fe310`: remove extra println left in by mistake
|
||||
- `feather-nrf52840`: add support for the Feather nRF52840
|
||||
- `maixbit`: add board definition and dummy runtime
|
||||
- `nintendoswitch`: Add experimental Nintendo Switch support without CRT
|
||||
- `nrf`: expose the RAM base address
|
||||
- `nrf`: add support for pin change interrupts
|
||||
- `nrf`: add microbit-s110v8 target
|
||||
- `nrf`: fix bug in SPI.Tx
|
||||
- `nrf`: support debugging the PCA10056
|
||||
- `pygamer`: add Adafruit PyGamer suport
|
||||
- `riscv`: fix interrupt configuration bug
|
||||
- `riscv`: disable linker relaxations during gp init
|
||||
- `stm32f4disco`: add new target with ST-Link v2.1 debugger
|
||||
- `teensy36`: add Teensy 3.6 support
|
||||
- `wasm`: fix event handling
|
||||
- `wasm`: add --no-demangle linker option
|
||||
- `wioterminal`: add support for the Seeed Wio Terminal
|
||||
- `xiao`: add support for the Seeed XIAO
|
||||
|
||||
0.13.1
|
||||
---
|
||||
* **standard library**
|
||||
- `runtime`: do not put scheduler and GC code in the same section
|
||||
- `runtime`: copy stack scan assembly for GBA
|
||||
* **boards**
|
||||
- `gameboy-advance`: always use ARM mode instead of Thumb mode
|
||||
|
||||
|
||||
0.13.0
|
||||
---
|
||||
* **command line**
|
||||
- use `gdb-multiarch` for debugging Cortex-M chips
|
||||
- support `tinygo run` with simavr
|
||||
- support LLVM 10
|
||||
- support Go 1.14
|
||||
- retry 3 times when attempting to do a 1200-baud reset
|
||||
* **compiler**
|
||||
- mark the `abort` function as noreturn
|
||||
- fix deferred calls to exported functions
|
||||
- add debug info for local variables
|
||||
- check for channel size limit
|
||||
- refactor coroutine lowering
|
||||
- add `dereferenceable_or_null` attribute to pointer parameters
|
||||
- do not perform nil checking when indexing slices and on `unsafe.Pointer`
|
||||
- remove `runtime.isnil` hack
|
||||
- use LLVM builtins for runtime `memcpy`/`memmove`/`memzero` functions
|
||||
- implement spec-compliant shifts on negative/overflow
|
||||
- support anonymous type asserts
|
||||
- track pointer result of string concatenation for GC
|
||||
- track PHI nodes for GC
|
||||
- add debug info to goroutine start wrappers
|
||||
- optimize comparing interface values against nil
|
||||
- fix miscompilation when deferring an interface call
|
||||
- builder: include picolibc for most baremetal targets
|
||||
- builder: run tools (clang, lld) as separate processes
|
||||
- builder: use `-fshort-enums` consistently
|
||||
- interp: add support for constant type asserts
|
||||
- interp: better support for interface operations
|
||||
- interp: include backtrace with error
|
||||
- transform: do not track const globals for GC
|
||||
- transform: replace panics with source locations
|
||||
- transform: fix error in interface lowering pass
|
||||
- transform: make coroutine lowering deterministic
|
||||
- transform: fix miscompilation in func lowering
|
||||
* **cgo**
|
||||
- make `-I` and `-L` paths absolute
|
||||
* **standard library**
|
||||
- `machine`: set the USB VID and PID to the manufacturer values
|
||||
- `machine`: correct USB CDC composite descriptors
|
||||
- `machine`: move `errors.New` calls to globals
|
||||
- `runtime`: support operations on nil maps
|
||||
- `runtime`: fix copy builtin return value on AVR
|
||||
- `runtime`: refactor goroutines
|
||||
- `runtime`: support `-scheduler=none` on most platforms
|
||||
- `runtime`: run package initialization in the main goroutine
|
||||
- `runtime`: export `malloc` / `free` for use from C
|
||||
- `runtime`: add garbage collector that uses an external allocator
|
||||
- `runtime`: scan callee-saved registers while marking the stack
|
||||
- `runtime`: remove recursion from conservative GC
|
||||
- `runtime`: fix blocking select on nil channel
|
||||
- `runtime/volatile`: include `ReplaceBits` method
|
||||
- `sync`: implement trivial `sync.Map`
|
||||
* **targets**
|
||||
- `arm`: use `-fomit-frame-pointer`
|
||||
- `atmega1284`: support this chip for testing purposes
|
||||
- `atsamd51`: make QSPI available on all boards
|
||||
- `atsamd51`: add support for ADC1
|
||||
- `atsamd51`: use new interrupt registration in UART code
|
||||
- `attiny`: clean up pin definitions
|
||||
- `avr`: use the correct RAM start address
|
||||
- `avr`: pass the correct `-mmcu` flag to the linker
|
||||
- `avr`: add support for tasks scheduler (disabled by default)
|
||||
- `avr`: fix linker problem with overlapping program/data areas
|
||||
- `nrf`: fix typo in pin configuration options
|
||||
- `nrf`: add lib/nrfx/mdk to include dirs
|
||||
- `nrf52840`: implement USB-CDC
|
||||
- `riscv`: implement VirtIO target and add RISC-V integration test
|
||||
- `riscv`: add I2C support for the HiFive1 rev B board
|
||||
- `stm32`: refactor GPIO pin handling
|
||||
- `stm32`: refactor UART code
|
||||
- `stm32f4`: add SPI
|
||||
- `wasm`: support Go 1.14 (breaking previous versions)
|
||||
- `wasm`: support `syscall/js.CopyBytesToJS`
|
||||
- `wasm`: sync polyfills from Go 1.14.
|
||||
* **boards**
|
||||
- `arduino-mega2560`: add the Arduino Mega 2560
|
||||
- `clue-alpha`: add the Adafruit CLUE Alpha
|
||||
- `gameboy-advance`: enable debugging with GDB
|
||||
- `particle-argon`: add the Particle Argon board
|
||||
- `particle-boron`: add the Particle Boron board
|
||||
- `particle-xenon`: add the Particle Xenon board
|
||||
- `reelboard`: add `reelboard-s140v7` SoftDevice target
|
||||
|
||||
0.12.0
|
||||
---
|
||||
* **command line**
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ Microcontrollers have lots of peripherals (I2C, SPI, ADC, etc.) and many don't h
|
||||
|
||||
## How to use our Github repository
|
||||
|
||||
The `master` 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.
|
||||
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:
|
||||
|
||||
|
||||
+1
-1
@@ -15,4 +15,4 @@ Ayke van Laethem <aykevanlaethem@gmail.com>
|
||||
Daniel Esteban <conejo@conejo.me>
|
||||
Loon, LLC.
|
||||
Ron Evans <ron@hybridgroup.com>
|
||||
Jaden Weiss <jaden@jadendw.dev>
|
||||
Nia Weiss <niaow1234@gmail.com>
|
||||
|
||||
+12
-9
@@ -1,10 +1,10 @@
|
||||
# TinyGo base stage installs Go 1.13, LLVM 9 and the TinyGo compiler itself.
|
||||
FROM golang:1.13 AS tinygo-base
|
||||
# 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
|
||||
|
||||
RUN wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-11 main" >> /etc/apt/sources.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y llvm-9-dev libclang-9-dev git
|
||||
apt-get install -y llvm-11-dev libclang-11-dev lld-11 git
|
||||
|
||||
COPY . /tinygo
|
||||
|
||||
@@ -12,8 +12,11 @@ COPY . /tinygo
|
||||
# after copying the tinygo directory in the previous step.
|
||||
RUN cd /tinygo/ && \
|
||||
rm -rf ./lib/* && \
|
||||
git submodule sync && \
|
||||
git submodule update --init --recursive --force
|
||||
|
||||
COPY ./lib/picolibc-include/* /tinygo/lib/picolibc-include/
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
go install /tinygo/
|
||||
|
||||
@@ -24,10 +27,10 @@ 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 wget -O- https://apt.llvm.org/llvm-snapshot.gpg.key| apt-key add - && \
|
||||
echo "deb http://apt.llvm.org/buster/ llvm-toolchain-buster-9 main" >> /etc/apt/sources.list && \
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y libllvm9 lld-9
|
||||
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
|
||||
@@ -58,7 +61,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils make clang-9 && \
|
||||
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.
|
||||
@@ -70,7 +73,7 @@ COPY --from=tinygo-base /tinygo/lib /tinygo/lib
|
||||
|
||||
RUN cd /tinygo/ && \
|
||||
apt-get update && \
|
||||
apt-get install -y apt-utils make clang-9 binutils-avr gcc-avr avr-libc && \
|
||||
apt-get install -y apt-utils make clang-11 binutils-avr gcc-avr avr-libc && \
|
||||
make gen-device
|
||||
|
||||
CMD ["tinygo"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Copyright (c) 2018-2019 TinyGo Authors. All rights reserved.
|
||||
Copyright (c) 2018-2021 TinyGo Authors. All rights reserved.
|
||||
|
||||
TinyGo includes portions of the Go standard library.
|
||||
Copyright (c) 2009-2019 The Go Authors. All rights reserved.
|
||||
Copyright (c) 2009-2021 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.
|
||||
|
||||
@@ -4,13 +4,15 @@ all: tinygo
|
||||
|
||||
# Default build and source directories, as created by `make llvm-build`.
|
||||
LLVM_BUILDDIR ?= llvm-build
|
||||
CLANG_SRC ?= llvm-project/clang
|
||||
LLD_SRC ?= llvm-project/lld
|
||||
LLVM_PROJECTDIR ?= llvm-project
|
||||
CLANG_SRC ?= $(LLVM_PROJECTDIR)/clang
|
||||
LLD_SRC ?= $(LLVM_PROJECTDIR)/lld
|
||||
|
||||
# Default tool selection.
|
||||
CLANG ?= clang-9
|
||||
LLVM_AR ?= llvm-ar-9
|
||||
LLVM_NM ?= llvm-nm-9
|
||||
# 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))
|
||||
|
||||
# Go binary and GOROOT to select
|
||||
GO ?= go
|
||||
@@ -20,10 +22,10 @@ export GOROOT = $(shell $(GO) env GOROOT)
|
||||
MD5SUM = md5sum
|
||||
|
||||
# tinygo binary for tests
|
||||
TINYGO ?= tinygo
|
||||
TINYGO ?= $(word 1,$(call detect,tinygo)$(call detect,build/tinygo))
|
||||
|
||||
# Use CCACHE for LLVM if possible
|
||||
ifneq (, $(shell which ccache))
|
||||
ifneq (, $(shell command -v ccache 2> /dev/null))
|
||||
LLVM_OPTION += '-DLLVM_CCACHE_BUILD=ON'
|
||||
endif
|
||||
|
||||
@@ -34,9 +36,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-avr
|
||||
.PHONY: all tinygo test $(LLVM_BUILDDIR) llvm-source clean fmt gen-device gen-device-nrf gen-device-nxp gen-device-avr
|
||||
|
||||
LLVM_COMPONENTS = all-targets analysis asmparser asmprinter bitreader bitwriter codegen core coroutines coverage debuginfodwarf executionengine 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 executionengine frontendopenmp instrumentation interpreter ipo irreader linker lto mc mcjit objcarcopts option profiledata scalaropts support target
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
EXE = .exe
|
||||
@@ -46,64 +48,69 @@ ifeq ($(OS),Windows_NT)
|
||||
# LLVM compiled using MinGW on Windows appears to have problems with threads.
|
||||
# Without this flag, linking results in errors like these:
|
||||
# libLLVMSupport.a(Threading.cpp.obj):Threading.cpp:(.text+0x55): undefined reference to `std::thread::hardware_concurrency()'
|
||||
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF
|
||||
LLVM_OPTION += -DLLVM_ENABLE_THREADS=OFF -DLLVM_ENABLE_PIC=OFF
|
||||
|
||||
CGO_CPPFLAGS += -DCINDEX_NO_EXPORTS
|
||||
CGO_LDFLAGS += -static -static-libgcc -static-libstdc++
|
||||
CGO_LDFLAGS_EXTRA += -lversion
|
||||
|
||||
# Build libclang manually because the CMake-based build system on Windows
|
||||
# doesn't allow building libclang as a static library.
|
||||
LIBCLANG_PATH = $(abspath build/libclang-custom.a)
|
||||
LIBCLANG_FILES = $(abspath $(wildcard $(LLVM_BUILDDIR)/tools/clang/tools/libclang/CMakeFiles/libclang.dir/*.cpp.obj))
|
||||
|
||||
# Add the libclang dependency to the tinygo binary target.
|
||||
tinygo: $(LIBCLANG_PATH)
|
||||
test: $(LIBCLANG_PATH)
|
||||
# Build libclang.
|
||||
$(LIBCLANG_PATH): $(LIBCLANG_FILES)
|
||||
@mkdir -p build
|
||||
ar rcs $(LIBCLANG_PATH) $^
|
||||
LIBCLANG_NAME = libclang
|
||||
|
||||
else ifeq ($(shell uname -s),Darwin)
|
||||
MD5SUM = md5
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
LIBCLANG_NAME = clang
|
||||
else ifeq ($(shell uname -s),FreeBSD)
|
||||
MD5SUM = md5
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
LIBCLANG_NAME = clang
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
else
|
||||
LIBCLANG_PATH = $(abspath $(LLVM_BUILDDIR))/lib/libclang.a
|
||||
LIBCLANG_NAME = clang
|
||||
START_GROUP = -Wl,--start-group
|
||||
END_GROUP = -Wl,--end-group
|
||||
endif
|
||||
|
||||
CLANG_LIBS = $(START_GROUP) -lclangAnalysis -lclangARCMigrate -lclangAST -lclangASTMatchers -lclangBasic -lclangCodeGen -lclangCrossTU -lclangDriver -lclangDynamicASTMatchers -lclangEdit -lclangFormat -lclangFrontend -lclangFrontendTool -lclangHandleCXX -lclangHandleLLVM -lclangIndex -lclangLex -lclangParse -lclangRewrite -lclangRewriteFrontend -lclangSema -lclangSerialization -lclangStaticAnalyzerCheckers -lclangStaticAnalyzerCore -lclangStaticAnalyzerFrontend -lclangTooling -lclangToolingASTDiff -lclangToolingCore -lclangToolingInclusions $(END_GROUP) -lstdc++
|
||||
# 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_LIBS = $(START_GROUP) $(addprefix -l,$(CLANG_LIB_NAMES)) $(END_GROUP) -lstdc++
|
||||
|
||||
LLD_LIBS = $(START_GROUP) -llldCOFF -llldCommon -llldCore -llldDriver -llldELF -llldMachO -llldMinGW -llldReaderWriter -llldWasm -llldYAML $(END_GROUP)
|
||||
# Libraries that should be linked in for the statically linked LLD.
|
||||
LLD_LIB_NAMES = lldCOFF lldCommon lldCore lldDriver lldELF lldMachO lldMinGW lldReaderWriter lldWasm lldYAML
|
||||
LLD_LIBS = $(START_GROUP) $(addprefix -l,$(LLD_LIB_NAMES)) $(END_GROUP)
|
||||
|
||||
# Other libraries that are needed to link TinyGo.
|
||||
EXTRA_LIB_NAMES = LLVMInterpreter
|
||||
|
||||
# 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)))
|
||||
|
||||
# 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++11
|
||||
CGO_LDFLAGS+=$(LIBCLANG_PATH) -std=c++11 -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_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)
|
||||
endif
|
||||
|
||||
|
||||
clean:
|
||||
@rm -rf build
|
||||
|
||||
FMT_PATHS = ./*.go builder cgo compiler interp ir 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/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-nrf gen-device-sam gen-device-sifive gen-device-stm32
|
||||
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-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
|
||||
$(GO) build -o ./build/gen-device-avr ./tools/gen-device-avr/
|
||||
./build/gen-device-avr lib/avr/packs/atmega src/device/avr/
|
||||
./build/gen-device-avr lib/avr/packs/tiny src/device/avr/
|
||||
@@ -112,10 +119,18 @@ gen-device-avr:
|
||||
build/gen-device-svd: ./tools/gen-device-svd/*.go
|
||||
$(GO) build -o $@ ./tools/gen-device-svd/
|
||||
|
||||
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/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/esp
|
||||
|
||||
gen-device-nrf: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/NordicSemiconductor/nrfx/tree/master/mdk lib/nrfx/mdk/ src/device/nrf/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/nrf
|
||||
|
||||
gen-device-nxp: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/NXP lib/cmsis-svd/data/NXP/ src/device/nxp/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/nxp
|
||||
|
||||
gen-device-sam: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Atmel lib/cmsis-svd/data/Atmel/ src/device/sam/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/sam
|
||||
@@ -124,48 +139,70 @@ gen-device-sifive: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/SiFive-Community -interrupts=software lib/cmsis-svd/data/SiFive-Community/ src/device/sifive/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/sifive
|
||||
|
||||
gen-device-kendryte: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/Kendryte-Community -interrupts=software lib/cmsis-svd/data/Kendryte-Community/ src/device/kendryte/
|
||||
GO111MODULE=off $(GO) fmt ./src/device/kendryte
|
||||
|
||||
gen-device-stm32: build/gen-device-svd
|
||||
./build/gen-device-svd -source=https://github.com/posborne/cmsis-svd/tree/master/data/STMicro lib/cmsis-svd/data/STMicro/ src/device/stm32/
|
||||
./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
|
||||
|
||||
|
||||
# Get LLVM sources.
|
||||
llvm-project/README.md:
|
||||
git clone -b release/9.x https://github.com/llvm/llvm-project
|
||||
llvm-source: llvm-project/README.md
|
||||
$(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
|
||||
|
||||
# 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-project/llvm "-DLLVM_TARGETS_TO_BUILD=X86;ARM;AArch64;RISCV;WebAssembly" "-DLLVM_EXPERIMENTAL_TARGETS_TO_BUILD=AVR" -DCMAKE_BUILD_TYPE=Release -DLIBCLANG_BUILD_STATIC=ON -DLLVM_ENABLE_TERMINFO=OFF -DLLVM_ENABLE_ZLIB=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 $(LLVM_OPTION)
|
||||
|
||||
# Build LLVM.
|
||||
$(LLVM_BUILDDIR): $(LLVM_BUILDDIR)/build.ninja
|
||||
cd $(LLVM_BUILDDIR); ninja
|
||||
cd $(LLVM_BUILDDIR); ninja $(NINJA_BUILD_TARGETS)
|
||||
|
||||
|
||||
# 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)
|
||||
|
||||
|
||||
# Build the Go compiler.
|
||||
tinygo:
|
||||
@if [ ! -f "$(LLVM_BUILDDIR)/bin/llvm-config" ]; then echo "Fetch and build LLVM first by running:"; echo " make llvm-source"; echo " make $(LLVM_BUILDDIR)"; exit 1; fi
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) build -o build/tinygo$(EXE) -tags byollvm .
|
||||
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 -tags byollvm ./cgo ./compileopts ./interp ./transform .
|
||||
CGO_CPPFLAGS="$(CGO_CPPFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GO) test -v -buildmode exe -tags byollvm ./cgo ./compileopts ./compiler ./interp ./transform .
|
||||
|
||||
# Test known-working standard library packages.
|
||||
# TODO: do this in one command, parallelize, and only show failing tests (no
|
||||
# implied -v flag).
|
||||
.PHONY: tinygo-test
|
||||
tinygo-test:
|
||||
cd tests/tinygotest && 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
|
||||
|
||||
.PHONY: smoketest
|
||||
smoketest:
|
||||
$(TINYGO) version
|
||||
# test all examples
|
||||
# test all examples (except pwm)
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/adc
|
||||
@@ -186,7 +223,9 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(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/pwm
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit-v2 examples/microbit-blink
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10040 examples/serial
|
||||
@$(MD5SUM) test.hex
|
||||
@@ -212,6 +251,8 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=microbit examples/echo
|
||||
@$(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=nrf52840-mdk examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pca10031 examples/blinky1
|
||||
@@ -238,10 +279,16 @@ smoketest:
|
||||
@$(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
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=clue-alpha examples/blinky1
|
||||
@$(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=itsybitsy-m4 examples/blinky1
|
||||
@@ -254,35 +301,107 @@ smoketest:
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pyportal examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=particle-argon examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=particle-boron examples/blinky1
|
||||
@$(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
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=reelboard-s140v7 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=wioterminal examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pygamer examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=xiao examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=circuitplay-express examples/dac
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=pyportal examples/dac
|
||||
@$(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=itsybitsy-nrf52840 examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=qtpy examples/blinky1
|
||||
@$(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
|
||||
# test pwm
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m0 examples/pwm
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=itsybitsy-m4 examples/pwm
|
||||
@$(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
|
||||
@$(MD5SUM) test.hex
|
||||
ifneq ($(AVR), 0)
|
||||
$(TINYGO) build -size short -o test.hex -target=atmega1284p examples/serial
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=arduino examples/pwm
|
||||
@$(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-nano examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=digispark examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
$(TINYGO) build -size short -o test.hex -target=digispark -gc=leaking examples/blinky1
|
||||
@$(MD5SUM) test.hex
|
||||
endif
|
||||
ifneq ($(XTENSA), 0)
|
||||
$(TINYGO) build -size short -o test.bin -target=esp32-mini32 examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
$(TINYGO) build -size short -o test.bin -target=nodemcu examples/blinky1
|
||||
@$(MD5SUM) test.bin
|
||||
endif
|
||||
$(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
|
||||
# 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 -o test.nro -target=nintendoswitch examples/serial
|
||||
@$(MD5SUM) test.nro
|
||||
|
||||
release: tinygo gen-device wasi-libc
|
||||
wasmtest:
|
||||
$(GO) test ./tests/wasm
|
||||
|
||||
build/release: tinygo gen-device wasi-libc
|
||||
@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/nrfx
|
||||
@mkdir -p build/release/tinygo/lib/picolibc/newlib/libc
|
||||
@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
|
||||
@@ -296,10 +415,28 @@ release: tinygo gen-device wasi-libc
|
||||
@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/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/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-builtins -target=armv6m-none-eabi -o build/release/tinygo/pkg/armv6m-none-eabi/compiler-rt.a
|
||||
./build/tinygo build-builtins -target=armv7m-none-eabi -o build/release/tinygo/pkg/armv7m-none-eabi/compiler-rt.a
|
||||
./build/tinygo build-builtins -target=armv7em-none-eabi -o build/release/tinygo/pkg/armv7em-none-eabi/compiler-rt.a
|
||||
./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
|
||||
|
||||
release: build/release
|
||||
tar -czf build/release.tar.gz -C build/release tinygo
|
||||
|
||||
deb: build/release
|
||||
@mkdir -p build/release-deb/usr/local/bin
|
||||
@mkdir -p build/release-deb/usr/local/lib
|
||||
cp -ar build/release/tinygo build/release-deb/usr/local/lib/tinygo
|
||||
ln -sf ../lib/tinygo/bin/tinygo build/release-deb/usr/local/bin/tinygo
|
||||
fpm -f -s dir -t deb -n tinygo -v $(shell grep "const Version = " goenv/version.go | awk '{print $$NF}') -m '@tinygo-org' --description='TinyGo is a Go compiler for small places.' --license='BSD 3-Clause' --url=https://tinygo.org/ --deb-changelog CHANGELOG.md -p build/release.deb -C ./build/release-deb
|
||||
|
||||
@@ -43,33 +43,59 @@ See the [getting started instructions](https://tinygo.org/getting-started/) for
|
||||
|
||||
You can compile TinyGo programs for microcontrollers, WebAssembly and Linux.
|
||||
|
||||
The following 27 microcontroller boards are currently supported:
|
||||
The following 53 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 nRF52840 Express](https://www.adafruit.com/product/4062)
|
||||
* [Adafruit Feather STM32F405 Express](https://www.adafruit.com/product/4382)
|
||||
* [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)
|
||||
* [Adafruit Matrix Portal M4](https://www.adafruit.com/product/4745)
|
||||
* [Adafruit Metro M4 Express Airlift](https://www.adafruit.com/product/4000)
|
||||
* [Adafruit PyBadge](https://www.adafruit.com/product/4200)
|
||||
* [Adafruit PyGamer](https://www.adafruit.com/product/4242)
|
||||
* [Adafruit PyPortal](https://www.adafruit.com/product/4116)
|
||||
* [Adafruit Trinket M0](https://www.adafruit.com/product/3500)
|
||||
* [Arduino Mega 2560](https://store.arduino.cc/arduino-mega-2560-rev3)
|
||||
* [Arduino MKR1000](https://store.arduino.cc/arduino-mkr1000-wifi)
|
||||
* [Arduino Nano](https://store.arduino.cc/arduino-nano)
|
||||
* [Arduino Nano33 IoT](https://store.arduino.cc/nano-33-iot)
|
||||
* [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/)
|
||||
* [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)
|
||||
* [Game Boy Advance](https://en.wikipedia.org/wiki/Game_Boy_Advance)
|
||||
* [Makerdiary nRF52840-MDK](https://wiki.makerdiary.com/nrf52840-mdk/)
|
||||
* [Makerdiary nRF52840-MDK USB Dongle](https://wiki.makerdiary.com/nrf52840-mdk-usb-dongle/)
|
||||
* [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)
|
||||
* [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/)
|
||||
* [Phytec reel board](https://www.phytec.eu/product-eu/internet-of-things/reelboard/)
|
||||
* [PineTime DevKit](https://www.pine64.org/pinetime/)
|
||||
* [PJRC Teensy 3.6](https://www.pjrc.com/store/teensy36.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)
|
||||
* [Seeed Seeeduino XIAO](https://www.seeedstudio.com/Seeeduino-XIAO-Arduino-Microcontroller-SAMD21-Cortex-M0+-p-4426.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)
|
||||
* [ST Micro "Nucleo F103RB"](https://www.st.com/en/evaluation-tools/nucleo-f103rb.html)
|
||||
* [ST Micro STM32F103XX "Bluepill"](http://wiki.stm32duino.com/index.php?title=Blue_Pill)
|
||||
* [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" L552ZE](https://www.st.com/en/evaluation-tools/nucleo-l552ze-q.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)
|
||||
* [X9 Pro smartwatch](https://github.com/curtpw/nRF5x-device-reverse-engineering/tree/master/X9-nrf52832-activity-tracker/)
|
||||
|
||||
@@ -131,4 +157,6 @@ The original reasoning was: if [Python](https://micropython.org/) can run on mic
|
||||
|
||||
This project is licensed under the BSD 3-clause license, just like the [Go project](https://golang.org/LICENSE) itself.
|
||||
|
||||
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/9.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
|
||||
Some code has been copied from the LLVM project and is therefore licensed under [a variant of the Apache 2.0 license](http://releases.llvm.org/11.0.0/LICENSE.TXT). This has been clearly indicated in the header of these files.
|
||||
|
||||
Some code has been copied and/or ported from Paul Stoffregen's Teensy libraries and is therefore licensed under PJRC's license. This has been clearly indicated in the header of these files.
|
||||
|
||||
+20
-16
@@ -1,7 +1,7 @@
|
||||
# Avoid lengthy LLVM rebuilds on each newly pushed branch. Pull requests will
|
||||
# be built anyway.
|
||||
trigger:
|
||||
- master
|
||||
- release
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
@@ -10,11 +10,15 @@ jobs:
|
||||
pool:
|
||||
vmImage: 'VS2017-Win2016'
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
inputs:
|
||||
version: '1.16'
|
||||
- checkout: self
|
||||
- task: CacheBeta@0
|
||||
fetchDepth: 1
|
||||
- task: Cache@2
|
||||
displayName: Cache LLVM source
|
||||
inputs:
|
||||
key: llvm-source-9-windows-v0
|
||||
key: llvm-source-11-windows-v1
|
||||
path: llvm-project
|
||||
- task: Bash@3
|
||||
displayName: Download LLVM source
|
||||
@@ -24,7 +28,7 @@ jobs:
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache LLVM build
|
||||
inputs:
|
||||
key: llvm-build-9-windows-v0
|
||||
key: llvm-build-11-windows-v4
|
||||
path: llvm-build
|
||||
- task: Bash@3
|
||||
displayName: Build LLVM
|
||||
@@ -33,34 +37,34 @@ jobs:
|
||||
script: |
|
||||
if [ ! -f llvm-build/lib/liblldELF.a ]
|
||||
then
|
||||
# install dependencies
|
||||
choco install ninja
|
||||
# LLVM 9 cannot be built with MinGW 8.
|
||||
# For details: https://reviews.llvm.org/D70266
|
||||
choco uninstall mingw
|
||||
choco install mingw --version=7.3.0
|
||||
# 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
|
||||
script: choco install qemu --version=2020.06.12
|
||||
- task: CacheBeta@0
|
||||
displayName: Cache wasi-libc sysroot
|
||||
inputs:
|
||||
key: wasi-libc-sysroot-v0
|
||||
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 CLANG=$PWD/llvm-build/bin/clang LLVM_AR=$PWD/llvm-build/bin/llvm-ar LLVM_NM=$PWD/llvm-build/bin/llvm-nm
|
||||
script: PATH=/usr/bin:$PATH make wasi-libc
|
||||
- task: Bash@3
|
||||
displayName: Test TinyGo
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
unset GOROOT
|
||||
make test
|
||||
- task: Bash@3
|
||||
@@ -68,9 +72,9 @@ jobs:
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
unset GOROOT
|
||||
make release -j4
|
||||
make build/release -j4
|
||||
- publish: $(System.DefaultWorkingDirectory)/build/release/tinygo
|
||||
displayName: Publish zip as artifact
|
||||
artifact: tinygo
|
||||
@@ -79,6 +83,6 @@ jobs:
|
||||
inputs:
|
||||
targetType: inline
|
||||
script: |
|
||||
export PATH="/c/Go1.13/bin:$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
export PATH="$PATH:./llvm-build/bin:/c/Program Files/qemu"
|
||||
unset GOROOT
|
||||
make smoketest TINYGO=build/tinygo AVR=0
|
||||
make smoketest TINYGO=build/tinygo AVR=0 XTENSA=0
|
||||
|
||||
+606
-138
@@ -4,11 +4,15 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -16,44 +20,379 @@ import (
|
||||
"github.com/tinygo-org/tinygo/compiler"
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
"github.com/tinygo-org/tinygo/interp"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"github.com/tinygo-org/tinygo/stacksize"
|
||||
"github.com/tinygo-org/tinygo/transform"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// BuildResult is the output of a build. This includes the binary itself and
|
||||
// some other metadata that is obtained while building the binary.
|
||||
type BuildResult struct {
|
||||
// A path to the output binary. It will be removed after Build returns, so
|
||||
// if it should be kept it must be copied or moved away.
|
||||
Binary string
|
||||
|
||||
// The directory of the main package. This is useful for testing as the test
|
||||
// binary must be run in the directory of the tested package.
|
||||
MainDir string
|
||||
}
|
||||
|
||||
// Build performs a single package to executable Go build. It takes in a package
|
||||
// name, an output path, and set of compile options and from that it manages the
|
||||
// whole compilation process.
|
||||
//
|
||||
// The error value may be of type *MultiError. Callers will likely want to check
|
||||
// for this case and print such errors individually.
|
||||
func Build(pkgName, outpath string, config *compileopts.Config, action func(string) error) error {
|
||||
c, err := compiler.NewCompiler(pkgName, config)
|
||||
func Build(pkgName, outpath string, config *compileopts.Config, action func(BuildResult) error) error {
|
||||
compilerConfig := &compiler.Config{
|
||||
Triple: config.Triple(),
|
||||
CPU: config.CPU(),
|
||||
Features: config.Features(),
|
||||
GOOS: config.GOOS(),
|
||||
GOARCH: config.GOARCH(),
|
||||
CodeModel: config.CodeModel(),
|
||||
RelocationModel: config.RelocationModel(),
|
||||
|
||||
Scheduler: config.Scheduler(),
|
||||
FuncImplementation: config.FuncImplementation(),
|
||||
AutomaticStackSize: config.AutomaticStackSize(),
|
||||
DefaultStackSize: config.Target.DefaultStackSize,
|
||||
NeedsStackObjects: config.NeedsStackObjects(),
|
||||
Debug: config.Debug(),
|
||||
}
|
||||
|
||||
// Load the target machine, which is the LLVM object that contains all
|
||||
// details of a target (alignment restrictions, pointer size, default
|
||||
// address spaces, etc).
|
||||
machine, err := compiler.NewTargetMachine(compilerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compile Go code to IR.
|
||||
errs := c.Compile(pkgName)
|
||||
if len(errs) != 0 {
|
||||
return newMultiError(errs)
|
||||
// Load entire program AST into memory.
|
||||
lprogram, err := loader.Load(config, []string{pkgName}, config.ClangHeaders, types.Config{
|
||||
Sizes: compiler.Sizes(machine),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = lprogram.Parse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The slice of jobs that orchestrates most of the build.
|
||||
// This is somewhat like an in-memory Makefile with each job being a
|
||||
// Makefile target.
|
||||
var jobs []*compileJob
|
||||
|
||||
// Add job to compile and optimize all Go files at once.
|
||||
// TODO: parallelize this.
|
||||
var mod llvm.Module
|
||||
var stackSizeLoads []string
|
||||
programJob := &compileJob{
|
||||
description: "compile Go files",
|
||||
run: func() (err error) {
|
||||
mod, err = compileWholeProgram(pkgName, config, compilerConfig, lprogram, machine)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Make sure stack sizes are loaded from a separate section so they can be
|
||||
// modified after linking.
|
||||
if config.AutomaticStackSize() {
|
||||
stackSizeLoads = transform.CreateStackSizeLoads(mod, config)
|
||||
}
|
||||
return
|
||||
},
|
||||
}
|
||||
jobs = append(jobs, programJob)
|
||||
|
||||
// Check whether we only need to create an object file.
|
||||
// If so, we don't need to link anything and will be finished quickly.
|
||||
outext := filepath.Ext(outpath)
|
||||
if outext == ".o" || outext == ".bc" || outext == ".ll" {
|
||||
// Run jobs to produce the LLVM module.
|
||||
err := runJobs(jobs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Generate output.
|
||||
switch outext {
|
||||
case ".o":
|
||||
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(outpath, llvmBuf.Bytes(), 0666)
|
||||
case ".bc":
|
||||
data := llvm.WriteBitcodeToMemoryBuffer(mod).Bytes()
|
||||
return ioutil.WriteFile(outpath, data, 0666)
|
||||
case ".ll":
|
||||
data := []byte(mod.String())
|
||||
return ioutil.WriteFile(outpath, data, 0666)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
// Act as a compiler driver, as we need to produce a complete executable.
|
||||
// First add all jobs necessary to build this object file, then afterwards
|
||||
// run all jobs in parallel as far as possible.
|
||||
|
||||
// Create a temporary directory for intermediary files.
|
||||
dir, err := ioutil.TempDir("", "tinygo")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Add job to write the output object file.
|
||||
objfile := filepath.Join(dir, "main.o")
|
||||
outputObjectFileJob := &compileJob{
|
||||
description: "generate output file",
|
||||
dependencies: []*compileJob{programJob},
|
||||
run: func() error {
|
||||
llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(objfile, llvmBuf.Bytes(), 0666)
|
||||
},
|
||||
}
|
||||
jobs = append(jobs, outputObjectFileJob)
|
||||
|
||||
// Prepare link command.
|
||||
linkerDependencies := []*compileJob{outputObjectFileJob}
|
||||
executable := filepath.Join(dir, "main")
|
||||
tmppath := executable // final file
|
||||
ldflags := append(config.LDFlags(), "-o", executable, objfile)
|
||||
|
||||
// Add compiler-rt dependency if needed. Usually this is a simple load from
|
||||
// a cache.
|
||||
if config.Target.RTLib == "compiler-rt" {
|
||||
path, job, err := CompilerRT.load(config.Triple(), config.CPU(), dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if job != nil {
|
||||
// The library was not loaded from cache so needs to be compiled
|
||||
// (and then stored in the cache).
|
||||
jobs = append(jobs, job.dependencies...)
|
||||
jobs = append(jobs, job)
|
||||
linkerDependencies = append(linkerDependencies, job)
|
||||
}
|
||||
ldflags = append(ldflags, path)
|
||||
}
|
||||
|
||||
// Add libc dependency if needed.
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
switch config.Target.Libc {
|
||||
case "picolibc":
|
||||
path, job, err := Picolibc.load(config.Triple(), config.CPU(), dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if job != nil {
|
||||
// The library needs to be compiled (cache miss).
|
||||
jobs = append(jobs, job.dependencies...)
|
||||
jobs = append(jobs, job)
|
||||
linkerDependencies = append(linkerDependencies, job)
|
||||
}
|
||||
ldflags = append(ldflags, path)
|
||||
case "wasi-libc":
|
||||
path := filepath.Join(root, "lib/wasi-libc/sysroot/lib/wasm32-wasi/libc.a")
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
return errors.New("could not find wasi-libc, perhaps you need to run `make wasi-libc`?")
|
||||
}
|
||||
ldflags = append(ldflags, path)
|
||||
case "":
|
||||
// no library specified, so nothing to do
|
||||
default:
|
||||
return fmt.Errorf("unknown libc: %s", config.Target.Libc)
|
||||
}
|
||||
|
||||
// Add jobs to compile extra files. These files are in C or assembly and
|
||||
// contain things like the interrupt vector table and low level operations
|
||||
// such as stack switching.
|
||||
for i, path := range config.ExtraFiles() {
|
||||
abspath := filepath.Join(root, path)
|
||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||
job := &compileJob{
|
||||
description: "compile extra file " + path,
|
||||
run: func() error {
|
||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
linkerDependencies = append(linkerDependencies, job)
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
|
||||
// Add jobs to compile C files in all packages. This is part of CGo.
|
||||
// TODO: do this as part of building the package to be able to link the
|
||||
// bitcode files together.
|
||||
for i, pkg := range lprogram.Sorted() {
|
||||
for j, filename := range pkg.CFiles {
|
||||
file := filepath.Join(pkg.Dir, filename)
|
||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"."+strconv.Itoa(j)+"-"+filepath.Base(file)+".o")
|
||||
job := &compileJob{
|
||||
description: "compile CGo file " + file,
|
||||
run: func() error {
|
||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, file)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", file, err}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
jobs = append(jobs, job)
|
||||
linkerDependencies = append(linkerDependencies, job)
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
}
|
||||
|
||||
// Linker flags from CGo lines:
|
||||
// #cgo LDFLAGS: foo
|
||||
if len(lprogram.LDFlags) > 0 {
|
||||
ldflags = append(ldflags, lprogram.LDFlags...)
|
||||
}
|
||||
|
||||
// Create a linker job, which links all object files together and does some
|
||||
// extra stuff that can only be done after linking.
|
||||
jobs = append(jobs, &compileJob{
|
||||
description: "link",
|
||||
dependencies: linkerDependencies,
|
||||
run: func() error {
|
||||
err = link(config.Target.Linker, ldflags...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to link", executable, err}
|
||||
}
|
||||
|
||||
var calculatedStacks []string
|
||||
var stackSizes map[string]functionStackSize
|
||||
if config.Options.PrintStacks || config.AutomaticStackSize() {
|
||||
// Try to determine stack sizes at compile time.
|
||||
// Don't do this by default as it usually doesn't work on
|
||||
// unsupported architectures.
|
||||
calculatedStacks, stackSizes, err = determineStackSizes(mod, executable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if config.AutomaticStackSize() {
|
||||
// Modify the .tinygo_stacksizes section that contains a stack size
|
||||
// for each goroutine.
|
||||
err = modifyStackSizes(executable, stackSizeLoads, stackSizes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not modify stack sizes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
|
||||
sizes, err := loadProgramSize(executable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Options.PrintSizes == "short" {
|
||||
fmt.Printf(" code data bss | flash ram\n")
|
||||
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
||||
} else {
|
||||
fmt.Printf(" code rodata data bss | flash ram | package\n")
|
||||
for _, name := range sizes.sortedPackageNames() {
|
||||
pkgSize := sizes.Packages[name]
|
||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
|
||||
}
|
||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
|
||||
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
||||
}
|
||||
}
|
||||
|
||||
// Print goroutine stack sizes, as far as possible.
|
||||
if config.Options.PrintStacks {
|
||||
printStacks(calculatedStacks, stackSizes)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
// Run all jobs to compile and link the program.
|
||||
// Do this now (instead of after elf-to-hex and similar conversions) as it
|
||||
// is simpler and cannot be parallelized.
|
||||
err = runJobs(jobs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get an Intel .hex file or .bin file from the .elf file.
|
||||
outputBinaryFormat := config.BinaryFormat(outext)
|
||||
switch outputBinaryFormat {
|
||||
case "elf":
|
||||
// do nothing, file is already in ELF format
|
||||
case "hex", "bin":
|
||||
// Extract raw binary, either encoding it as a hex file or as a raw
|
||||
// firmware file.
|
||||
tmppath = filepath.Join(dir, "main"+outext)
|
||||
err := objcopy(executable, tmppath, outputBinaryFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "uf2":
|
||||
// Get UF2 from the .elf file.
|
||||
tmppath = filepath.Join(dir, "main"+outext)
|
||||
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case "esp32", "esp8266":
|
||||
// Special format for the ESP family of chips (parsed by the ROM
|
||||
// bootloader).
|
||||
tmppath = filepath.Join(dir, "main"+outext)
|
||||
err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unknown output binary format: %s", outputBinaryFormat)
|
||||
}
|
||||
return action(BuildResult{
|
||||
Binary: tmppath,
|
||||
MainDir: lprogram.MainPkg().Dir,
|
||||
})
|
||||
}
|
||||
|
||||
// compileWholeProgram compiles the entire *loader.Program to a LLVM module and
|
||||
// applies most necessary optimizations and transformations.
|
||||
func compileWholeProgram(pkgName string, config *compileopts.Config, compilerConfig *compiler.Config, lprogram *loader.Program, machine llvm.TargetMachine) (llvm.Module, error) {
|
||||
// Compile AST to IR.
|
||||
mod, errs := compiler.CompileProgram(lprogram, machine, compilerConfig, config.DumpSSA())
|
||||
if errs != nil {
|
||||
return mod, newMultiError(errs)
|
||||
}
|
||||
|
||||
if config.Options.PrintIR {
|
||||
fmt.Println("; Generated LLVM IR:")
|
||||
fmt.Println(c.IR())
|
||||
fmt.Println(mod.String())
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after IR construction")
|
||||
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
||||
return mod, errors.New("verification error after IR construction")
|
||||
}
|
||||
|
||||
err = interp.Run(c.Module(), config.DumpSSA())
|
||||
err := interp.Run(mod, config.DumpSSA())
|
||||
if err != nil {
|
||||
return err
|
||||
return mod, err
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after interpreting runtime.initAll")
|
||||
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
||||
return mod, errors.New("verification error after interpreting runtime.initAll")
|
||||
}
|
||||
|
||||
if config.GOOS() != "darwin" {
|
||||
c.ApplyFunctionSections() // -ffunction-sections
|
||||
transform.ApplyFunctionSections(mod) // -ffunction-sections
|
||||
}
|
||||
|
||||
// Browsers cannot handle external functions that have type i64 because it
|
||||
@@ -61,10 +400,10 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
|
||||
// keep functions interoperable, pass int64 types as pointers to
|
||||
// stack-allocated values.
|
||||
// Use -wasm-abi=generic to disable this behaviour.
|
||||
if config.Options.WasmAbi == "js" && strings.HasPrefix(config.Triple(), "wasm") {
|
||||
err := transform.ExternalInt64AsPtr(c.Module())
|
||||
if config.WasmAbi() == "js" {
|
||||
err := transform.ExternalInt64AsPtr(mod)
|
||||
if err != nil {
|
||||
return err
|
||||
return mod, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,147 +411,276 @@ func Build(pkgName, outpath string, config *compileopts.Config, action func(stri
|
||||
// exactly.
|
||||
errs = nil
|
||||
switch config.Options.Opt {
|
||||
case "none:", "0":
|
||||
errs = c.Optimize(0, 0, 0) // -O0
|
||||
/*
|
||||
Currently, turning optimizations off causes compile failures.
|
||||
We rely on the optimizer removing some dead symbols.
|
||||
Avoid providing an option that does not work right now.
|
||||
In the future once everything has been fixed we can re-enable this.
|
||||
|
||||
case "none", "0":
|
||||
errs = transform.Optimize(mod, config, 0, 0, 0) // -O0
|
||||
*/
|
||||
case "1":
|
||||
errs = c.Optimize(1, 0, 0) // -O1
|
||||
errs = transform.Optimize(mod, config, 1, 0, 0) // -O1
|
||||
case "2":
|
||||
errs = c.Optimize(2, 0, 225) // -O2
|
||||
errs = transform.Optimize(mod, config, 2, 0, 225) // -O2
|
||||
case "s":
|
||||
errs = c.Optimize(2, 1, 225) // -Os
|
||||
errs = transform.Optimize(mod, config, 2, 1, 225) // -Os
|
||||
case "z":
|
||||
errs = c.Optimize(2, 2, 5) // -Oz, default
|
||||
errs = transform.Optimize(mod, config, 2, 2, 5) // -Oz, default
|
||||
default:
|
||||
errs = []error{errors.New("unknown optimization level: -opt=" + config.Options.Opt)}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return newMultiError(errs)
|
||||
return mod, newMultiError(errs)
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification failure after LLVM optimization passes")
|
||||
if err := llvm.VerifyModule(mod, llvm.PrintMessageAction); err != nil {
|
||||
return mod, errors.New("verification failure after LLVM optimization passes")
|
||||
}
|
||||
|
||||
// On the AVR, pointers can point either to flash or to RAM, but we don't
|
||||
// know. As a temporary fix, load all global variables in RAM.
|
||||
// In the future, there should be a compiler pass that determines which
|
||||
// pointers are flash and which are in RAM so that pointers can have a
|
||||
// correct address space parameter (address space 1 is for flash).
|
||||
if strings.HasPrefix(config.Triple(), "avr") {
|
||||
c.NonConstGlobals()
|
||||
if err := c.Verify(); err != nil {
|
||||
return errors.New("verification error after making all globals non-constant on AVR")
|
||||
}
|
||||
// LLVM 11 by default tries to emit tail calls (even with the target feature
|
||||
// disabled) unless it is explicitly disabled with a function attribute.
|
||||
// This is a problem, as it tries to emit them and prints an error when it
|
||||
// can't with this feature disabled.
|
||||
// Because as of september 2020 tail calls are not yet widely supported,
|
||||
// they need to be disabled until they are widely supported (at which point
|
||||
// the +tail-call target feautre can be set).
|
||||
if strings.HasPrefix(config.Triple(), "wasm") {
|
||||
transform.DisableTailCalls(mod)
|
||||
}
|
||||
|
||||
// Generate output.
|
||||
outext := filepath.Ext(outpath)
|
||||
switch outext {
|
||||
case ".o":
|
||||
return c.EmitObject(outpath)
|
||||
case ".bc":
|
||||
return c.EmitBitcode(outpath)
|
||||
case ".ll":
|
||||
return c.EmitText(outpath)
|
||||
default:
|
||||
// Act as a compiler driver.
|
||||
return mod, nil
|
||||
}
|
||||
|
||||
// Create a temporary directory for intermediary files.
|
||||
dir, err := ioutil.TempDir("", "tinygo")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
// functionStackSizes keeps stack size information about a single function
|
||||
// (usually a goroutine).
|
||||
type functionStackSize struct {
|
||||
humanName string
|
||||
stackSize uint64
|
||||
stackSizeType stacksize.SizeType
|
||||
missingStackSize *stacksize.CallNode
|
||||
}
|
||||
|
||||
// Write the object file.
|
||||
objfile := filepath.Join(dir, "main.o")
|
||||
err = c.EmitObject(objfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load builtins library from the cache, possibly compiling it on the
|
||||
// fly.
|
||||
var librt string
|
||||
if config.Target.RTLib == "compiler-rt" {
|
||||
librt, err = loadBuiltins(config.Triple())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare link command.
|
||||
executable := filepath.Join(dir, "main")
|
||||
tmppath := executable // final file
|
||||
ldflags := append(config.LDFlags(), "-o", executable, objfile)
|
||||
if config.Target.RTLib == "compiler-rt" {
|
||||
ldflags = append(ldflags, librt)
|
||||
}
|
||||
|
||||
// Compile extra files.
|
||||
root := goenv.Get("TINYGOROOT")
|
||||
for i, path := range config.ExtraFiles() {
|
||||
abspath := filepath.Join(root, path)
|
||||
outpath := filepath.Join(dir, "extra-"+strconv.Itoa(i)+"-"+filepath.Base(path)+".o")
|
||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, abspath)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
}
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
|
||||
// Compile C files in packages.
|
||||
for i, pkg := range c.Packages() {
|
||||
for _, file := range pkg.CFiles {
|
||||
path := filepath.Join(pkg.Package.Dir, file)
|
||||
outpath := filepath.Join(dir, "pkg"+strconv.Itoa(i)+"-"+file+".o")
|
||||
err := runCCompiler(config.Target.Compiler, append(config.CFlags(), "-c", "-o", outpath, path)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", path, err}
|
||||
// determineStackSizes tries to determine the stack sizes of all started
|
||||
// goroutines and of the reset vector. The LLVM module is necessary to find
|
||||
// functions that call a function pointer.
|
||||
func determineStackSizes(mod llvm.Module, executable string) ([]string, map[string]functionStackSize, error) {
|
||||
var callsIndirectFunction []string
|
||||
gowrappers := []string{}
|
||||
gowrapperNames := make(map[string]string)
|
||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
// Determine which functions call a function pointer.
|
||||
for bb := fn.FirstBasicBlock(); !bb.IsNil(); bb = llvm.NextBasicBlock(bb) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst.IsACallInst().IsNil() {
|
||||
continue
|
||||
}
|
||||
ldflags = append(ldflags, outpath)
|
||||
}
|
||||
}
|
||||
|
||||
// Link the object files together.
|
||||
err = link(config.Target.Linker, ldflags...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to link", executable, err}
|
||||
}
|
||||
|
||||
if config.Options.PrintSizes == "short" || config.Options.PrintSizes == "full" {
|
||||
sizes, err := loadProgramSize(executable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.Options.PrintSizes == "short" {
|
||||
fmt.Printf(" code data bss | flash ram\n")
|
||||
fmt.Printf("%7d %7d %7d | %7d %7d\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
||||
} else {
|
||||
fmt.Printf(" code rodata data bss | flash ram | package\n")
|
||||
for _, name := range sizes.sortedPackageNames() {
|
||||
pkgSize := sizes.Packages[name]
|
||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | %s\n", pkgSize.Code, pkgSize.ROData, pkgSize.Data, pkgSize.BSS, pkgSize.Flash(), pkgSize.RAM(), name)
|
||||
if callee := inst.CalledValue(); callee.IsAFunction().IsNil() && callee.IsAInlineAsm().IsNil() {
|
||||
callsIndirectFunction = append(callsIndirectFunction, fn.Name())
|
||||
}
|
||||
fmt.Printf("%7d %7d %7d %7d | %7d %7d | (sum)\n", sizes.Sum.Code, sizes.Sum.ROData, sizes.Sum.Data, sizes.Sum.BSS, sizes.Sum.Flash(), sizes.Sum.RAM())
|
||||
fmt.Printf("%7d - %7d %7d | %7d %7d | (all)\n", sizes.Code, sizes.Data, sizes.BSS, sizes.Code+sizes.Data, sizes.Data+sizes.BSS)
|
||||
}
|
||||
}
|
||||
|
||||
// Get an Intel .hex file or .bin file from the .elf file.
|
||||
if outext == ".hex" || outext == ".bin" || outext == ".gba" {
|
||||
tmppath = filepath.Join(dir, "main"+outext)
|
||||
err := objcopy(executable, tmppath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if outext == ".uf2" {
|
||||
// Get UF2 from the .elf file.
|
||||
tmppath = filepath.Join(dir, "main"+outext)
|
||||
err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Get a list of "go wrappers", small wrapper functions that decode
|
||||
// parameters when starting a new goroutine.
|
||||
attr := fn.GetStringAttributeAtIndex(-1, "tinygo-gowrapper")
|
||||
if !attr.IsNil() {
|
||||
gowrappers = append(gowrappers, fn.Name())
|
||||
gowrapperNames[fn.Name()] = attr.GetStringValue()
|
||||
}
|
||||
}
|
||||
sort.Strings(gowrappers)
|
||||
|
||||
// Load the ELF binary.
|
||||
f, err := elf.Open(executable)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not load executable for stack size analysis: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
// Determine the frame size of each function (if available) and the callgraph.
|
||||
functions, err := stacksize.CallGraph(f, callsIndirectFunction)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not parse executable for stack size analysis: %w", err)
|
||||
}
|
||||
|
||||
// Goroutines need to be started and finished and take up some stack space
|
||||
// that way. This can be measured by measuing the stack size of
|
||||
// tinygo_startTask.
|
||||
if numFuncs := len(functions["tinygo_startTask"]); numFuncs != 1 {
|
||||
return nil, nil, fmt.Errorf("expected exactly one definition of tinygo_startTask, got %d", numFuncs)
|
||||
}
|
||||
baseStackSize, baseStackSizeType, baseStackSizeFailedAt := functions["tinygo_startTask"][0].StackSize()
|
||||
|
||||
sizes := make(map[string]functionStackSize)
|
||||
|
||||
// Add the reset handler function, for convenience. The reset handler runs
|
||||
// startup code and the scheduler. The listed stack size is not the full
|
||||
// stack size: interrupts are not counted.
|
||||
var resetFunction string
|
||||
switch f.Machine {
|
||||
case elf.EM_ARM:
|
||||
// Note: all interrupts happen on this stack so the real size is bigger.
|
||||
resetFunction = "Reset_Handler"
|
||||
}
|
||||
if resetFunction != "" {
|
||||
funcs := functions[resetFunction]
|
||||
if len(funcs) != 1 {
|
||||
return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", resetFunction, len(funcs))
|
||||
}
|
||||
stackSize, stackSizeType, missingStackSize := funcs[0].StackSize()
|
||||
sizes[resetFunction] = functionStackSize{
|
||||
stackSize: stackSize,
|
||||
stackSizeType: stackSizeType,
|
||||
missingStackSize: missingStackSize,
|
||||
humanName: resetFunction,
|
||||
}
|
||||
}
|
||||
|
||||
// Add all goroutine wrapper functions.
|
||||
for _, name := range gowrappers {
|
||||
funcs := functions[name]
|
||||
if len(funcs) != 1 {
|
||||
return nil, nil, fmt.Errorf("expected exactly one definition of %s in the callgraph, found %d", name, len(funcs))
|
||||
}
|
||||
humanName := gowrapperNames[name]
|
||||
if humanName == "" {
|
||||
humanName = name // fallback
|
||||
}
|
||||
stackSize, stackSizeType, missingStackSize := funcs[0].StackSize()
|
||||
if baseStackSizeType != stacksize.Bounded {
|
||||
// It was not possible to determine the stack size at compile time
|
||||
// because tinygo_startTask does not have a fixed stack size. This
|
||||
// can happen when using -opt=1.
|
||||
stackSizeType = baseStackSizeType
|
||||
missingStackSize = baseStackSizeFailedAt
|
||||
} else if stackSize < baseStackSize {
|
||||
// This goroutine has a very small stack, but still needs to fit all
|
||||
// registers to start and suspend the goroutine. Otherwise a stack
|
||||
// overflow will occur even before the goroutine is started.
|
||||
stackSize = baseStackSize
|
||||
}
|
||||
sizes[name] = functionStackSize{
|
||||
stackSize: stackSize,
|
||||
stackSizeType: stackSizeType,
|
||||
missingStackSize: missingStackSize,
|
||||
humanName: humanName,
|
||||
}
|
||||
}
|
||||
|
||||
if resetFunction != "" {
|
||||
return append([]string{resetFunction}, gowrappers...), sizes, nil
|
||||
}
|
||||
return gowrappers, sizes, nil
|
||||
}
|
||||
|
||||
// modifyStackSizes modifies the .tinygo_stacksizes section with the updated
|
||||
// stack size information. Before this modification, all stack sizes in the
|
||||
// section assume the default stack size (which is relatively big).
|
||||
func modifyStackSizes(executable string, stackSizeLoads []string, stackSizes map[string]functionStackSize) error {
|
||||
fp, err := os.OpenFile(executable, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
elfFile, err := elf.NewFile(fp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
section := elfFile.Section(".tinygo_stacksizes")
|
||||
if section == nil {
|
||||
return errors.New("could not find .tinygo_stacksizes section")
|
||||
}
|
||||
|
||||
if section.Size != section.FileSize {
|
||||
// Sanity check.
|
||||
return fmt.Errorf("expected .tinygo_stacksizes to have identical size and file size, got %d and %d", section.Size, section.FileSize)
|
||||
}
|
||||
|
||||
// Read all goroutine stack sizes.
|
||||
data := make([]byte, section.Size)
|
||||
_, err = fp.ReadAt(data, int64(section.Offset))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(stackSizeLoads)*4 != len(data) {
|
||||
// Note: while AVR should use 2 byte stack sizes, even 64-bit platforms
|
||||
// should probably stick to 4 byte stack sizes as a larger than 4GB
|
||||
// stack doesn't make much sense.
|
||||
return errors.New("expected 4 byte stack sizes")
|
||||
}
|
||||
|
||||
// Modify goroutine stack sizes with a compile-time known worst case stack
|
||||
// size.
|
||||
for i, name := range stackSizeLoads {
|
||||
fn, ok := stackSizes[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("could not find symbol %s in ELF file", name)
|
||||
}
|
||||
if fn.stackSizeType == stacksize.Bounded {
|
||||
stackSize := uint32(fn.stackSize)
|
||||
|
||||
// Adding 4 for the stack canary. Even though the size may be
|
||||
// automatically determined, stack overflow checking is still
|
||||
// important as the stack size cannot be determined for all
|
||||
// goroutines.
|
||||
stackSize += 4
|
||||
|
||||
// Add stack size used by interrupts.
|
||||
switch elfFile.Machine {
|
||||
case elf.EM_ARM:
|
||||
// On Cortex-M (assumed here), this stack size is 8 words or 32
|
||||
// bytes. This is only to store the registers that the interrupt
|
||||
// may modify, the interrupt will switch to the interrupt stack
|
||||
// (MSP).
|
||||
// Some background:
|
||||
// https://interrupt.memfault.com/blog/cortex-m-rtos-context-switching
|
||||
stackSize += 32
|
||||
}
|
||||
|
||||
// Finally write the stack size to the binary.
|
||||
binary.LittleEndian.PutUint32(data[i*4:], stackSize)
|
||||
}
|
||||
}
|
||||
|
||||
// Write back the modified stack sizes.
|
||||
_, err = fp.WriteAt(data, int64(section.Offset))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// printStacks prints the maximum stack depth for functions that are started as
|
||||
// goroutines. Stack sizes cannot always be determined statically, in particular
|
||||
// recursive functions and functions that call interface methods or function
|
||||
// pointers may have an unknown stack depth (depending on what the optimizer
|
||||
// manages to optimize away).
|
||||
//
|
||||
// It might print something like the following:
|
||||
//
|
||||
// function stack usage (in bytes)
|
||||
// Reset_Handler 316
|
||||
// examples/blinky2.led1 92
|
||||
// runtime.run$1 300
|
||||
func printStacks(calculatedStacks []string, stackSizes map[string]functionStackSize) {
|
||||
// Print the sizes of all stacks.
|
||||
fmt.Printf("%-32s %s\n", "function", "stack usage (in bytes)")
|
||||
for _, name := range calculatedStacks {
|
||||
fn := stackSizes[name]
|
||||
switch fn.stackSizeType {
|
||||
case stacksize.Bounded:
|
||||
fmt.Printf("%-32s %d\n", fn.humanName, fn.stackSize)
|
||||
case stacksize.Unknown:
|
||||
fmt.Printf("%-32s unknown, %s does not have stack frame information\n", fn.humanName, fn.missingStackSize)
|
||||
case stacksize.Recursive:
|
||||
fmt.Printf("%-32s recursive, %s may call itself\n", fn.humanName, fn.missingStackSize)
|
||||
case stacksize.IndirectCall:
|
||||
fmt.Printf("%-32s unknown, %s calls a function pointer\n", fn.humanName, fn.missingStackSize)
|
||||
}
|
||||
return action(tmppath)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-19
@@ -30,10 +30,7 @@ func cacheTimestamp(paths []string) (time.Time, error) {
|
||||
// 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.
|
||||
//
|
||||
// TODO: the configKey is currently ignored. It is supposed to be used as extra
|
||||
// data for the cache key, like the compiler version and arguments.
|
||||
func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
|
||||
func cacheLoad(name string, sourceFiles []string) (string, error) {
|
||||
cachepath := filepath.Join(goenv.Get("GOCACHE"), name)
|
||||
cacheStat, err := os.Stat(cachepath)
|
||||
if os.IsNotExist(err) {
|
||||
@@ -58,9 +55,7 @@ func cacheLoad(name, configKey string, sourceFiles []string) (string, error) {
|
||||
|
||||
// Store the file located at tmppath in the cache with the given name. The
|
||||
// tmppath may or may not be gone afterwards.
|
||||
//
|
||||
// Note: the configKey is ignored, see cacheLoad.
|
||||
func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string, error) {
|
||||
func cacheStore(tmppath, name string, sourceFiles []string) (string, error) {
|
||||
// get the last modified time
|
||||
if len(sourceFiles) == 0 {
|
||||
panic("cache: no source files")
|
||||
@@ -74,24 +69,16 @@ func cacheStore(tmppath, name, configKey string, sourceFiles []string) (string,
|
||||
return "", err
|
||||
}
|
||||
cachepath := filepath.Join(dir, name)
|
||||
err = moveFile(tmppath, cachepath)
|
||||
err = copyFile(tmppath, cachepath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cachepath, nil
|
||||
}
|
||||
|
||||
// moveFile renames the file from src to dst. If renaming doesn't work (for
|
||||
// example, the rename crosses a filesystem boundary), the file is copied and
|
||||
// the old file is removed.
|
||||
func moveFile(src, dst string) error {
|
||||
err := os.Rename(src, dst)
|
||||
if err == nil {
|
||||
// Success!
|
||||
return nil
|
||||
}
|
||||
// Failed to move, probably a different filesystem.
|
||||
// Do a copy + remove.
|
||||
// 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
|
||||
|
||||
+15
-105
@@ -1,12 +1,7 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// These are the GENERIC_SOURCES according to CMakeList.txt.
|
||||
@@ -156,105 +151,20 @@ var aeabiBuiltins = []string{
|
||||
"arm/aeabi_uldivmod.S",
|
||||
}
|
||||
|
||||
func builtinFiles(target string) []string {
|
||||
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
|
||||
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
||||
builtins = append(builtins, aeabiBuiltins...)
|
||||
}
|
||||
return builtins
|
||||
}
|
||||
|
||||
// builtinsDir returns the directory where the sources for compiler-rt are kept.
|
||||
func builtinsDir() string {
|
||||
return filepath.Join(goenv.Get("TINYGOROOT"), "lib", "compiler-rt", "lib", "builtins")
|
||||
}
|
||||
|
||||
// Get the builtins archive, possibly generating it as needed.
|
||||
func loadBuiltins(target string) (path string, err error) {
|
||||
// Try to load a precompiled compiler-rt library.
|
||||
precompiledPath := filepath.Join(goenv.Get("TINYGOROOT"), "pkg", target, "compiler-rt.a")
|
||||
if _, err := os.Stat(precompiledPath); err == nil {
|
||||
// Found a precompiled compiler-rt for this OS/architecture. Return the
|
||||
// path directly.
|
||||
return precompiledPath, nil
|
||||
}
|
||||
|
||||
outfile := "librt-" + target + ".a"
|
||||
builtinsDir := builtinsDir()
|
||||
|
||||
builtins := builtinFiles(target)
|
||||
srcs := make([]string, len(builtins))
|
||||
for i, name := range builtins {
|
||||
srcs[i] = filepath.Join(builtinsDir, name)
|
||||
}
|
||||
|
||||
if path, err := cacheLoad(outfile, commands["clang"][0], srcs); path != "" || err != nil {
|
||||
return path, err
|
||||
}
|
||||
|
||||
var cachepath string
|
||||
err = CompileBuiltins(target, func(path string) error {
|
||||
path, err := cacheStore(path, outfile, commands["clang"][0], srcs)
|
||||
cachepath = path
|
||||
return err
|
||||
})
|
||||
return cachepath, err
|
||||
}
|
||||
|
||||
// CompileBuiltins compiles builtins from compiler-rt into a static library.
|
||||
// When it succeeds, it will call the callback with the resulting path. The path
|
||||
// will be removed after callback returns. If callback returns an error, this is
|
||||
// passed through to the return value of this function.
|
||||
func CompileBuiltins(target string, callback func(path string) error) error {
|
||||
builtinsDir := builtinsDir()
|
||||
|
||||
builtins := builtinFiles(target)
|
||||
srcs := make([]string, len(builtins))
|
||||
for i, name := range builtins {
|
||||
srcs[i] = filepath.Join(builtinsDir, name)
|
||||
}
|
||||
|
||||
dirPrefix := "tinygo-builtins"
|
||||
remapDir := filepath.Join(os.TempDir(), dirPrefix)
|
||||
dir, err := ioutil.TempDir(os.TempDir(), dirPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
// Compile all builtins.
|
||||
// TODO: use builtins optimized for a given target if available.
|
||||
objs := make([]string, 0, len(builtins))
|
||||
for _, name := range builtins {
|
||||
objname := name
|
||||
if strings.LastIndexByte(objname, '/') >= 0 {
|
||||
objname = objname[strings.LastIndexByte(objname, '/'):]
|
||||
// CompilerRT is a library with symbols required by programs compiled with LLVM.
|
||||
// These symbols are for operations that cannot be emitted with a single
|
||||
// instruction or a short sequence of instructions for that target.
|
||||
//
|
||||
// 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"} },
|
||||
sourceDir: "lib/compiler-rt/lib/builtins",
|
||||
sources: func(target string) []string {
|
||||
builtins := append([]string{}, genericBuiltins...) // copy genericBuiltins
|
||||
if strings.HasPrefix(target, "arm") || strings.HasPrefix(target, "thumb") {
|
||||
builtins = append(builtins, aeabiBuiltins...)
|
||||
}
|
||||
objpath := filepath.Join(dir, objname+".o")
|
||||
objs = append(objs, objpath)
|
||||
srcpath := filepath.Join(builtinsDir, name)
|
||||
// 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 := []string{"-c", "-Oz", "-g", "-Werror", "-Wall", "-std=c11", "-fshort-enums", "-nostdlibinc", "-ffunction-sections", "-fdata-sections", "-Wno-macro-redefined", "--target=" + target, "-fdebug-prefix-map=" + dir + "=" + remapDir}
|
||||
if strings.HasPrefix(target, "riscv32-") {
|
||||
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
|
||||
}
|
||||
err := runCCompiler("clang", append(args, "-o", objpath, srcpath)...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
}
|
||||
|
||||
// Put all the object files in a single archive. This archive file will be
|
||||
// used to statically link compiler-rt.
|
||||
arpath := filepath.Join(dir, "librt.a")
|
||||
err = makeArchive(arpath, objs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Give the caller the resulting file. The callback must copy the file,
|
||||
// because after it returns the temporary directory will be removed.
|
||||
return callback(arpath)
|
||||
return builtins
|
||||
},
|
||||
}
|
||||
|
||||
+45
-36
@@ -48,6 +48,7 @@
|
||||
#include "llvm/Support/Host.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/Process.h"
|
||||
#include "llvm/Support/Signals.h"
|
||||
#include "llvm/Support/SourceMgr.h"
|
||||
#include "llvm/Support/TargetRegistry.h"
|
||||
@@ -70,12 +71,12 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
bool Success = true;
|
||||
|
||||
// Parse the arguments.
|
||||
std::unique_ptr<OptTable> OptTbl(createDriverOptTable());
|
||||
const OptTable &OptTbl = getDriverOptTable();
|
||||
|
||||
const unsigned IncludedFlagsBitmask = options::CC1AsOption;
|
||||
unsigned MissingArgIndex, MissingArgCount;
|
||||
InputArgList Args = OptTbl->ParseArgs(Argv, MissingArgIndex, MissingArgCount,
|
||||
IncludedFlagsBitmask);
|
||||
InputArgList Args = OptTbl.ParseArgs(Argv, MissingArgIndex, MissingArgCount,
|
||||
IncludedFlagsBitmask);
|
||||
|
||||
// Check for missing argument error.
|
||||
if (MissingArgCount) {
|
||||
@@ -88,7 +89,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
for (const Arg *A : Args.filtered(OPT_UNKNOWN)) {
|
||||
auto ArgString = A->getAsString(Args);
|
||||
std::string Nearest;
|
||||
if (OptTbl->findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
|
||||
if (OptTbl.findNearest(ArgString, Nearest, IncludedFlagsBitmask) > 1)
|
||||
Diags.Report(diag::err_drv_unknown_argument) << ArgString;
|
||||
else
|
||||
Diags.Report(diag::err_drv_unknown_argument_with_suggestion)
|
||||
@@ -100,7 +101,7 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
|
||||
// Target Options
|
||||
Opts.Triple = llvm::Triple::normalize(Args.getLastArgValue(OPT_triple));
|
||||
Opts.CPU = Args.getLastArgValue(OPT_target_cpu);
|
||||
Opts.CPU = std::string(Args.getLastArgValue(OPT_target_cpu));
|
||||
Opts.Features = Args.getAllArgValues(OPT_target_feature);
|
||||
|
||||
// Use the default target triple if unspecified.
|
||||
@@ -131,13 +132,19 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
|
||||
Opts.RelaxELFRelocations = Args.hasArg(OPT_mrelax_relocations);
|
||||
Opts.DwarfVersion = getLastArgIntValue(Args, OPT_dwarf_version_EQ, 2, Diags);
|
||||
Opts.DwarfDebugFlags = Args.getLastArgValue(OPT_dwarf_debug_flags);
|
||||
Opts.DwarfDebugProducer = Args.getLastArgValue(OPT_dwarf_debug_producer);
|
||||
Opts.DebugCompilationDir = Args.getLastArgValue(OPT_fdebug_compilation_dir);
|
||||
Opts.MainFileName = Args.getLastArgValue(OPT_main_file_name);
|
||||
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));
|
||||
Opts.MainFileName = std::string(Args.getLastArgValue(OPT_main_file_name));
|
||||
|
||||
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ))
|
||||
Opts.DebugPrefixMap.insert(StringRef(Arg).split('='));
|
||||
for (const auto &Arg : Args.getAllArgValues(OPT_fdebug_prefix_map_EQ)) {
|
||||
auto Split = StringRef(Arg).split('=');
|
||||
Opts.DebugPrefixMap.insert(
|
||||
{std::string(Split.first), std::string(Split.second)});
|
||||
}
|
||||
|
||||
// Frontend Options
|
||||
if (Args.hasArg(OPT_INPUT)) {
|
||||
@@ -153,8 +160,9 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
}
|
||||
}
|
||||
Opts.LLVMArgs = Args.getAllArgValues(OPT_mllvm);
|
||||
Opts.OutputPath = Args.getLastArgValue(OPT_o);
|
||||
Opts.SplitDwarfOutput = Args.getLastArgValue(OPT_split_dwarf_output);
|
||||
Opts.OutputPath = std::string(Args.getLastArgValue(OPT_o));
|
||||
Opts.SplitDwarfOutput =
|
||||
std::string(Args.getLastArgValue(OPT_split_dwarf_output));
|
||||
if (Arg *A = Args.getLastArg(OPT_filetype)) {
|
||||
StringRef Name = A->getValue();
|
||||
unsigned OutputType = StringSwitch<unsigned>(Name)
|
||||
@@ -181,8 +189,10 @@ bool AssemblerInvocation::CreateFromArgs(AssemblerInvocation &Opts,
|
||||
Opts.RelaxAll = Args.hasArg(OPT_mrelax_all);
|
||||
Opts.NoExecStack = Args.hasArg(OPT_mno_exec_stack);
|
||||
Opts.FatalWarnings = Args.hasArg(OPT_massembler_fatal_warnings);
|
||||
Opts.RelocationModel = Args.getLastArgValue(OPT_mrelocation_model, "pic");
|
||||
Opts.TargetABI = Args.getLastArgValue(OPT_target_abi);
|
||||
Opts.NoWarn = Args.hasArg(OPT_massembler_no_warn);
|
||||
Opts.RelocationModel =
|
||||
std::string(Args.getLastArgValue(OPT_mrelocation_model, "pic"));
|
||||
Opts.TargetABI = std::string(Args.getLastArgValue(OPT_target_abi));
|
||||
Opts.IncrementalLinkerCompatible =
|
||||
Args.hasArg(OPT_mincremental_linker_compatible);
|
||||
Opts.SymbolDefs = Args.getAllArgValues(OPT_defsym);
|
||||
@@ -208,8 +218,8 @@ getOutputStream(StringRef Path, DiagnosticsEngine &Diags, bool Binary) {
|
||||
sys::RemoveFileOnSignal(Path);
|
||||
|
||||
std::error_code EC;
|
||||
auto Out = llvm::make_unique<raw_fd_ostream>(
|
||||
Path, EC, (Binary ? sys::fs::F_None : sys::fs::F_Text));
|
||||
auto Out = std::make_unique<raw_fd_ostream>(
|
||||
Path, EC, (Binary ? sys::fs::OF_None : sys::fs::OF_Text));
|
||||
if (EC) {
|
||||
Diags.Report(diag::err_fe_unable_to_open_output) << Path << EC.message();
|
||||
return nullptr;
|
||||
@@ -245,7 +255,9 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
std::unique_ptr<MCRegisterInfo> MRI(TheTarget->createMCRegInfo(Opts.Triple));
|
||||
assert(MRI && "Unable to create target register info!");
|
||||
|
||||
std::unique_ptr<MCAsmInfo> MAI(TheTarget->createMCAsmInfo(*MRI, Opts.Triple));
|
||||
MCTargetOptions MCOptions;
|
||||
std::unique_ptr<MCAsmInfo> MAI(
|
||||
TheTarget->createMCAsmInfo(*MRI, Opts.Triple, MCOptions));
|
||||
assert(MAI && "Unable to create target asm info!");
|
||||
|
||||
// Ensure MCAsmInfo initialization occurs before any use, otherwise sections
|
||||
@@ -269,7 +281,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
// MCObjectFileInfo needs a MCContext reference in order to initialize itself.
|
||||
std::unique_ptr<MCObjectFileInfo> MOFI(new MCObjectFileInfo());
|
||||
|
||||
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr);
|
||||
MCContext Ctx(MAI.get(), MRI.get(), MOFI.get(), &SrcMgr, &MCOptions);
|
||||
|
||||
bool PIC = false;
|
||||
if (Opts.RelocationModel == "static") {
|
||||
@@ -310,12 +322,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
SrcMgr.getMemoryBuffer(BufferIndex)->getBuffer());
|
||||
|
||||
// Build up the feature string from the target feature list.
|
||||
std::string FS;
|
||||
if (!Opts.Features.empty()) {
|
||||
FS = Opts.Features[0];
|
||||
for (unsigned i = 1, e = Opts.Features.size(); i != e; ++i)
|
||||
FS += "," + Opts.Features[i];
|
||||
}
|
||||
std::string FS = llvm::join(Opts.Features, ",");
|
||||
|
||||
std::unique_ptr<MCStreamer> Str;
|
||||
|
||||
@@ -326,7 +333,8 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
raw_pwrite_stream *Out = FDOS.get();
|
||||
std::unique_ptr<buffer_ostream> BOS;
|
||||
|
||||
MCTargetOptions MCOptions;
|
||||
MCOptions.MCNoWarn = Opts.NoWarn;
|
||||
MCOptions.MCFatalWarnings = Opts.FatalWarnings;
|
||||
MCOptions.ABIName = Opts.TargetABI;
|
||||
|
||||
// FIXME: There is a bit of code duplication with addPassesToEmitFile.
|
||||
@@ -340,7 +348,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
std::unique_ptr<MCAsmBackend> MAB(
|
||||
TheTarget->createMCAsmBackend(*STI, *MRI, MCOptions));
|
||||
|
||||
auto FOut = llvm::make_unique<formatted_raw_ostream>(*Out);
|
||||
auto FOut = std::make_unique<formatted_raw_ostream>(*Out);
|
||||
Str.reset(TheTarget->createAsmStreamer(
|
||||
Ctx, std::move(FOut), /*asmverbose*/ true,
|
||||
/*useDwarfDirectory*/ true, IP, std::move(CE), std::move(MAB),
|
||||
@@ -351,7 +359,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
assert(Opts.OutputType == AssemblerInvocation::FT_Obj &&
|
||||
"Invalid file type!");
|
||||
if (!FDOS->supportsSeeking()) {
|
||||
BOS = make_unique<buffer_ostream>(*FDOS);
|
||||
BOS = std::make_unique<buffer_ostream>(*FDOS);
|
||||
Out = BOS.get();
|
||||
}
|
||||
|
||||
@@ -378,7 +386,7 @@ bool ExecuteAssembler(AssemblerInvocation &Opts, DiagnosticsEngine &Diags) {
|
||||
MCSection *AsmLabel = Ctx.getMachOSection(
|
||||
"__LLVM", "__asm", MachO::S_REGULAR, 4, SectionKind::getReadOnly());
|
||||
Str.get()->SwitchSection(AsmLabel);
|
||||
Str.get()->EmitZeros(1);
|
||||
Str.get()->emitZeros(1);
|
||||
}
|
||||
|
||||
// Assembly to object compilation should leverage assembly info.
|
||||
@@ -436,7 +444,7 @@ static void LLVMErrorHandler(void *UserData, const std::string &Message,
|
||||
Diags.Report(diag::err_fe_error_backend) << Message;
|
||||
|
||||
// We cannot recover from llvm errors.
|
||||
exit(1);
|
||||
sys::Process::Exit(1);
|
||||
}
|
||||
|
||||
int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
||||
@@ -464,11 +472,11 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
||||
return 1;
|
||||
|
||||
if (Asm.ShowHelp) {
|
||||
std::unique_ptr<OptTable> Opts(driver::createDriverOptTable());
|
||||
Opts->PrintHelp(llvm::outs(), "clang -cc1as [options] file...",
|
||||
"Clang Integrated Assembler",
|
||||
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
|
||||
/*ShowAllAliases=*/false);
|
||||
getDriverOptTable().PrintHelp(
|
||||
llvm::outs(), "clang -cc1as [options] file...",
|
||||
"Clang Integrated Assembler",
|
||||
/*Include=*/driver::options::CC1AsOption, /*Exclude=*/0,
|
||||
/*ShowAllAliases=*/false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -485,7 +493,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
||||
// FIXME: Remove this, one day.
|
||||
if (!Asm.LLVMArgs.empty()) {
|
||||
unsigned NumArgs = Asm.LLVMArgs.size();
|
||||
auto Args = llvm::make_unique<const char*[]>(NumArgs + 2);
|
||||
auto Args = std::make_unique<const char*[]>(NumArgs + 2);
|
||||
Args[0] = "clang (LLVM option parsing)";
|
||||
for (unsigned i = 0; i != NumArgs; ++i)
|
||||
Args[i + 1] = Asm.LLVMArgs[i].c_str();
|
||||
@@ -499,6 +507,7 @@ int cc1as_main(ArrayRef<const char *> Argv, const char *Argv0, void *MainAddr) {
|
||||
// If any timers were active but haven't been destroyed yet, print their
|
||||
// results now.
|
||||
TimerGroup::printAll(errs());
|
||||
TimerGroup::clearAll();
|
||||
|
||||
return !!Failed;
|
||||
}
|
||||
|
||||
+2
-2
@@ -11,8 +11,6 @@
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
|
||||
/// Helper class for representing a single invocation of the assembler.
|
||||
struct AssemblerInvocation {
|
||||
/// @name Target Options
|
||||
@@ -82,6 +80,7 @@ struct AssemblerInvocation {
|
||||
unsigned RelaxAll : 1;
|
||||
unsigned NoExecStack : 1;
|
||||
unsigned FatalWarnings : 1;
|
||||
unsigned NoWarn : 1;
|
||||
unsigned IncrementalLinkerCompatible : 1;
|
||||
unsigned EmbedBitcode : 1;
|
||||
|
||||
@@ -107,6 +106,7 @@ public:
|
||||
RelaxAll = 0;
|
||||
NoExecStack = 0;
|
||||
FatalWarnings = 0;
|
||||
NoWarn = 0;
|
||||
IncrementalLinkerCompatible = 0;
|
||||
DwarfVersion = 0;
|
||||
EmbedBitcode = 0;
|
||||
|
||||
+4
-3
@@ -11,6 +11,7 @@
|
||||
#include <clang/FrontendTool/Utils.h>
|
||||
#include <llvm/ADT/IntrusiveRefCntPtr.h>
|
||||
#include <llvm/Option/Option.h>
|
||||
#include <llvm/Support/Host.h>
|
||||
|
||||
using namespace llvm;
|
||||
using namespace clang;
|
||||
@@ -52,8 +53,7 @@ bool tinygo_clang_driver(int argc, char **argv) {
|
||||
std::unique_ptr<clang::CompilerInstance> Clang(new clang::CompilerInstance());
|
||||
bool success = clang::CompilerInvocation::CreateFromArgs(
|
||||
Clang->getInvocation(),
|
||||
const_cast<const char **>(CCArgs.data()),
|
||||
const_cast<const char **>(CCArgs.data()) + CCArgs.size(),
|
||||
CCArgs,
|
||||
Diags);
|
||||
if (!success) {
|
||||
return false;
|
||||
@@ -74,7 +74,8 @@ bool tinygo_clang_driver(int argc, char **argv) {
|
||||
} else if (strcmp(*CCArgs.data(), "-cc1as") == 0) {
|
||||
// This is the assembler frontend. Parse the arguments.
|
||||
AssemblerInvocation Asm;
|
||||
if (!AssemblerInvocation::CreateFromArgs(Asm, llvm::ArrayRef<const char*>(CCArgs).slice(1), Diags))
|
||||
ArrayRef<const char *> Argv = llvm::ArrayRef<const char*>(CCArgs);
|
||||
if (!AssemblerInvocation::CreateFromArgs(Asm, Argv.slice(1), Diags))
|
||||
return false;
|
||||
|
||||
// Execute the invocation, unless there were parsing errors.
|
||||
|
||||
+17
-13
@@ -6,25 +6,28 @@ import (
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Commands lists command alternatives for various operating systems. These
|
||||
// commands may have a slightly different name across operating systems and
|
||||
// distributions or may not even exist in $PATH, in which case absolute paths
|
||||
// may be used.
|
||||
var commands = map[string][]string{
|
||||
"clang": {"clang-9"},
|
||||
"ld.lld": {"ld.lld-9", "ld.lld"},
|
||||
"wasm-ld": {"wasm-ld-9", "wasm-ld"},
|
||||
}
|
||||
var commands = map[string][]string{}
|
||||
|
||||
func init() {
|
||||
// Add the path to a Homebrew-installed LLVM 9 for ease of use (no need to
|
||||
llvmMajor := strings.Split(llvm.Version, ".")[0]
|
||||
commands["clang"] = []string{"clang-" + llvmMajor}
|
||||
commands["ld.lld"] = []string{"ld.lld-" + llvmMajor, "ld.lld"}
|
||||
commands["wasm-ld"] = []string{"wasm-ld-" + llvmMajor, "wasm-ld"}
|
||||
// Add the path to a Homebrew-installed LLVM for ease of use (no need to
|
||||
// manually set $PATH).
|
||||
if runtime.GOOS == "darwin" {
|
||||
commands["clang"] = append(commands["clang"], "/usr/local/opt/llvm@9/bin/clang-9")
|
||||
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/opt/llvm@9/bin/ld.lld")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/opt/llvm@9/bin/wasm-ld")
|
||||
prefix := "/usr/local/opt/llvm@" + llvmMajor + "/bin/"
|
||||
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")
|
||||
}
|
||||
// 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
|
||||
@@ -34,11 +37,12 @@ func init() {
|
||||
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")
|
||||
}
|
||||
// Add the path to the llvm90 installed from ports
|
||||
// Add the path to LLVM installed from ports.
|
||||
if runtime.GOOS == "freebsd" {
|
||||
commands["clang"] = append(commands["clang"], "/usr/local/llvm90/bin/clang-9")
|
||||
commands["ld.lld"] = append(commands["ld.lld"], "/usr/local/llvm90/bin/ld.lld")
|
||||
commands["wasm-ld"] = append(commands["wasm-ld"], "/usr/local/llvm90/bin/wasm-ld")
|
||||
prefix := "/usr/local/llvm" + llvmMajor + "/bin/"
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
// +build byollvm
|
||||
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CXXFLAGS: -fno-rtti
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
bool tinygo_clang_driver(int argc, char **argv);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// runCCompiler invokes a C compiler with the given arguments.
|
||||
//
|
||||
// This version invokes the built-in Clang when trying to run the Clang compiler.
|
||||
func runCCompiler(command string, flags ...string) error {
|
||||
switch command {
|
||||
case "clang":
|
||||
// Compile this with the internal Clang compiler.
|
||||
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
if headerPath == "" {
|
||||
return errors.New("could not locate Clang headers")
|
||||
}
|
||||
flags = append(flags, "-I"+headerPath)
|
||||
flags = append([]string{"tinygo:" + command}, flags...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
|
||||
for i, flag := range flags {
|
||||
cflag := C.CString(flag)
|
||||
cflags[i] = cflag
|
||||
defer C.free(unsafe.Pointer(cflag))
|
||||
}
|
||||
ok := C.tinygo_clang_driver(C.int(len(flags)), (**C.char)(buf))
|
||||
if !ok {
|
||||
return errors.New("failed to compile using built-in clang")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// +build !byollvm
|
||||
|
||||
package builder
|
||||
|
||||
// This file provides a way to a C compiler as an external command. See also:
|
||||
// clang-external.go
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// runCCompiler invokes a C compiler with the given arguments.
|
||||
//
|
||||
// This version always runs the compiler as an external command.
|
||||
func runCCompiler(command string, flags ...string) error {
|
||||
if cmdNames, ok := commands[command]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
cmd := exec.Command(command, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
+3
-3
@@ -21,12 +21,12 @@ func NewConfig(options *compileopts.Options) (*compileopts.Config, error) {
|
||||
if goroot == "" {
|
||||
return nil, errors.New("cannot locate $GOROOT, please set it manually")
|
||||
}
|
||||
major, minor, err := getGorootVersion(goroot)
|
||||
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 != 11 && minor != 12 && minor != 13) {
|
||||
return nil, fmt.Errorf("requires go version 1.11, 1.12, or 1.13, got go%d.%d", major, minor)
|
||||
if major != 1 || minor < 11 || minor > 16 {
|
||||
return nil, fmt.Errorf("requires go version 1.11 through 1.16, got go%d.%d", major, minor)
|
||||
}
|
||||
clangHeaderPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
return &compileopts.Config{
|
||||
|
||||
+28
-66
@@ -1,71 +1,16 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// getGorootVersion returns the major and minor version for a given GOROOT path.
|
||||
// If the goroot cannot be determined, (0, 0) is returned.
|
||||
func getGorootVersion(goroot string) (major, minor int, err error) {
|
||||
s, err := GorootVersionString(goroot)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if s == "" || s[:2] != "go" {
|
||||
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
|
||||
}
|
||||
|
||||
parts := strings.Split(s[2:], ".")
|
||||
if len(parts) < 2 {
|
||||
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
|
||||
}
|
||||
|
||||
// Ignore the errors, we don't really handle errors here anyway.
|
||||
var trailing string
|
||||
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
|
||||
if n == 2 && err == io.EOF {
|
||||
// Means there were no trailing characters (i.e., not an alpha/beta)
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GorootVersionString returns the version string as reported by the Go
|
||||
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
|
||||
// can have some variations (for beta releases, for example).
|
||||
func GorootVersionString(goroot string) (string, error) {
|
||||
if data, err := ioutil.ReadFile(filepath.Join(
|
||||
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
|
||||
|
||||
r := regexp.MustCompile("const TheVersion = `(.*)`")
|
||||
matches := r.FindSubmatch(data)
|
||||
if len(matches) != 2 {
|
||||
return "", errors.New("Invalid go version output:\n" + string(data))
|
||||
}
|
||||
|
||||
return string(matches[1]), nil
|
||||
|
||||
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
|
||||
return string(data), nil
|
||||
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// getClangHeaderPath returns the path to the built-in Clang headers. It tries
|
||||
// multiple locations, which should make it find the directory when installed in
|
||||
// various ways.
|
||||
@@ -84,6 +29,7 @@ func getClangHeaderPath(TINYGOROOT string) string {
|
||||
|
||||
// It looks like we are built with a system-installed LLVM. Do a last
|
||||
// attempt: try to use Clang headers relative to the clang binary.
|
||||
llvmMajor := strings.Split(llvm.Version, ".")[0]
|
||||
for _, cmdName := range commands["clang"] {
|
||||
binpath, err := exec.LookPath(cmdName)
|
||||
if err == nil {
|
||||
@@ -98,22 +44,38 @@ func getClangHeaderPath(TINYGOROOT string) string {
|
||||
// Example executable:
|
||||
// /usr/lib/llvm-9/bin/clang
|
||||
// Example include path:
|
||||
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
|
||||
// /usr/lib/llvm-9/lib64/clang/9.0.1/include/
|
||||
llvmRoot := filepath.Dir(filepath.Dir(binpath))
|
||||
clangVersionRoot := filepath.Join(llvmRoot, "lib", "clang")
|
||||
dirs, err := ioutil.ReadDir(clangVersionRoot)
|
||||
if err != nil {
|
||||
clangVersionRoot := filepath.Join(llvmRoot, "lib64", "clang")
|
||||
dirs64, err64 := ioutil.ReadDir(clangVersionRoot)
|
||||
// Example include path:
|
||||
// /usr/lib/llvm-9/lib/clang/9.0.1/include/
|
||||
clangVersionRoot = filepath.Join(llvmRoot, "lib", "clang")
|
||||
dirs32, err32 := ioutil.ReadDir(clangVersionRoot)
|
||||
if err64 != nil && err32 != nil {
|
||||
// Unexpected.
|
||||
continue
|
||||
}
|
||||
dirnames := make([]string, len(dirs))
|
||||
for i, d := range dirs {
|
||||
dirnames[i] = d.Name()
|
||||
dirnames := make([]string, len(dirs64)+len(dirs32))
|
||||
dirCount := 0
|
||||
for _, d := range dirs32 {
|
||||
name := d.Name()
|
||||
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
|
||||
dirnames[dirCount] = filepath.Join(llvmRoot, "lib", "clang", name)
|
||||
dirCount++
|
||||
}
|
||||
}
|
||||
for _, d := range dirs64 {
|
||||
name := d.Name()
|
||||
if name == llvmMajor || strings.HasPrefix(name, llvmMajor+".") {
|
||||
dirnames[dirCount] = filepath.Join(llvmRoot, "lib64", "clang", name)
|
||||
dirCount++
|
||||
}
|
||||
}
|
||||
sort.Strings(dirnames)
|
||||
// Check for the highest version first.
|
||||
for i := len(dirnames) - 1; i >= 0; i-- {
|
||||
path := filepath.Join(clangVersionRoot, dirnames[i], "include")
|
||||
for i := dirCount - 1; i >= 0; i-- {
|
||||
path := filepath.Join(dirnames[i], "include")
|
||||
_, err := os.Stat(filepath.Join(path, "stdint.h"))
|
||||
if err == nil {
|
||||
return path
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package builder
|
||||
|
||||
// This file implements support for writing ESP image files. These image files
|
||||
// are read by the ROM bootloader so have to be in a particular format.
|
||||
//
|
||||
// In the future, it may be necessary to implement support for other image
|
||||
// formats, such as the ESP8266 image formats (again, used by the ROM bootloader
|
||||
// to load the firmware).
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"debug/elf"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"sort"
|
||||
)
|
||||
|
||||
type espImageSegment struct {
|
||||
addr uint32
|
||||
data []byte
|
||||
}
|
||||
|
||||
// makeESPFirmare converts an input ELF file to an image file for an ESP32 or
|
||||
// ESP8266 chip. This is a special purpose image format just for the ESP chip
|
||||
// family, and is parsed by the on-chip mask ROM bootloader.
|
||||
//
|
||||
// The following documentation has been used:
|
||||
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
|
||||
// https://github.com/espressif/esp-idf/blob/8fbb63c2a701c22ccf4ce249f43aded73e134a34/components/bootloader_support/include/esp_image_format.h#L58
|
||||
// https://github.com/espressif/esptool/blob/master/esptool.py
|
||||
func makeESPFirmareImage(infile, outfile, format string) error {
|
||||
inf, err := elf.Open(infile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer inf.Close()
|
||||
|
||||
// Load all segments to be written to the image. These are actually ELF
|
||||
// sections, not true ELF segments (similar to how esptool does it).
|
||||
var segments []*espImageSegment
|
||||
for _, section := range inf.Sections {
|
||||
if section.Type != elf.SHT_PROGBITS || section.Size == 0 || section.Flags&elf.SHF_ALLOC == 0 {
|
||||
continue
|
||||
}
|
||||
data, err := section.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read section data: %w", err)
|
||||
}
|
||||
for len(data)%4 != 0 {
|
||||
// Align segment to 4 bytes.
|
||||
data = append(data, 0)
|
||||
}
|
||||
if uint64(uint32(section.Addr)) != section.Addr {
|
||||
return fmt.Errorf("section address too big: 0x%x", section.Addr)
|
||||
}
|
||||
segments = append(segments, &espImageSegment{
|
||||
addr: uint32(section.Addr),
|
||||
data: data,
|
||||
})
|
||||
}
|
||||
|
||||
// Sort the segments by address. This is what esptool does too.
|
||||
sort.SliceStable(segments, func(i, j int) bool { return segments[i].addr < segments[j].addr })
|
||||
|
||||
// Calculate checksum over the segment data. This is used in the image
|
||||
// footer.
|
||||
checksum := uint8(0xef)
|
||||
for _, segment := range segments {
|
||||
for _, b := range segment.data {
|
||||
checksum ^= b
|
||||
}
|
||||
}
|
||||
|
||||
// Write first to an in-memory buffer, primarily so that we can easily
|
||||
// calculate a hash over the entire image.
|
||||
// An added benefit is that we don't need to check for errors all the time.
|
||||
outf := &bytes.Buffer{}
|
||||
|
||||
// Image header.
|
||||
switch format {
|
||||
case "esp32":
|
||||
// Header format:
|
||||
// https://github.com/espressif/esp-idf/blob/8fbb63c2/components/bootloader_support/include/esp_image_format.h#L58
|
||||
binary.Write(outf, binary.LittleEndian, struct {
|
||||
magic uint8
|
||||
segment_count uint8
|
||||
spi_mode uint8
|
||||
spi_speed_size uint8
|
||||
entry_addr uint32
|
||||
wp_pin uint8
|
||||
spi_pin_drv [3]uint8
|
||||
reserved [11]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
|
||||
entry_addr: uint32(inf.Entry),
|
||||
wp_pin: 0xEE, // disable WP pin
|
||||
hash_appended: true, // add a SHA256 hash
|
||||
})
|
||||
case "esp8266":
|
||||
// Header format:
|
||||
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format
|
||||
// Basically a truncated version of the ESP32 header.
|
||||
binary.Write(outf, binary.LittleEndian, struct {
|
||||
magic uint8
|
||||
segment_count uint8
|
||||
spi_mode uint8
|
||||
spi_speed_size uint8
|
||||
entry_addr uint32
|
||||
}{
|
||||
magic: 0xE9,
|
||||
segment_count: byte(len(segments)),
|
||||
spi_mode: 0, // irrelevant, replaced by esptool when flashing
|
||||
spi_speed_size: 0x20, // spi_speed, spi_size: replaced by esptool when flashing
|
||||
entry_addr: uint32(inf.Entry),
|
||||
})
|
||||
default:
|
||||
return fmt.Errorf("builder: unknown binary format %#v, expected esp32 or esp8266", format)
|
||||
}
|
||||
|
||||
// Write all segments to the image.
|
||||
// https://github.com/espressif/esptool/wiki/Firmware-Image-Format#segment
|
||||
for _, segment := range segments {
|
||||
binary.Write(outf, binary.LittleEndian, struct {
|
||||
addr uint32
|
||||
length uint32
|
||||
}{
|
||||
addr: segment.addr,
|
||||
length: uint32(len(segment.data)),
|
||||
})
|
||||
outf.Write(segment.data)
|
||||
}
|
||||
|
||||
// Footer, including checksum.
|
||||
// The entire image size must be a multiple of 16, so pad the image to one
|
||||
// byte less than that before writing the checksum.
|
||||
outf.Write(make([]byte, 15-outf.Len()%16))
|
||||
outf.WriteByte(checksum)
|
||||
|
||||
if format == "esp32" {
|
||||
// SHA256 hash (to protect against image corruption, not for security).
|
||||
hash := sha256.Sum256(outf.Bytes())
|
||||
outf.Write(hash[:])
|
||||
}
|
||||
|
||||
// Write the image to the output file.
|
||||
return ioutil.WriteFile(outfile, outf.Bytes(), 0666)
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
package builder
|
||||
|
||||
// This file implements a job runner for the compiler, which runs jobs in
|
||||
// parallel while taking care of dependencies.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Set to true to enable logging in the job runner. This may help to debug
|
||||
// concurrency or performance issues.
|
||||
const jobRunnerDebug = false
|
||||
|
||||
type jobState uint8
|
||||
|
||||
const (
|
||||
jobStateQueued jobState = iota // not yet running
|
||||
jobStateRunning // running
|
||||
jobStateFinished // finished running
|
||||
)
|
||||
|
||||
// compileJob is a single compiler job, comparable to a single Makefile target.
|
||||
// It is used to orchestrate various compiler tasks that can be run in parallel
|
||||
// but that have dependencies and thus have limitations in how they can be run.
|
||||
type compileJob struct {
|
||||
description string // description, only used for logging
|
||||
dependencies []*compileJob
|
||||
run func() error
|
||||
state jobState
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
if jobRunnerDebug {
|
||||
fmt.Println("## start: ", jobToRun.description)
|
||||
}
|
||||
jobToRun.state = jobStateRunning
|
||||
workerChan <- jobToRun
|
||||
numRunningJobs++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
if jobRunnerDebug {
|
||||
fmt.Println("## finished:", job.description, "(time "+job.duration.String()+")")
|
||||
}
|
||||
if job.err != nil {
|
||||
// Wait for running jobs to finish.
|
||||
for numRunningJobs != 0 {
|
||||
<-doneChan
|
||||
numRunningJobs--
|
||||
}
|
||||
// Return error of first failing job.
|
||||
return job.err
|
||||
}
|
||||
}
|
||||
|
||||
// Some statistics, if debugging.
|
||||
if jobRunnerDebug {
|
||||
// Total duration of running all jobs.
|
||||
duration := time.Since(start)
|
||||
fmt.Println("## total: ", duration)
|
||||
|
||||
// The individual time of each job combined. On a multicore system, this
|
||||
// should be lower than the total above.
|
||||
fmt.Println("## job sum: ", totalTime)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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()
|
||||
if err != nil {
|
||||
job.err = err
|
||||
}
|
||||
job.duration = time.Since(start)
|
||||
doneChan <- job
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Library is a container for information about a single C library, such as a
|
||||
// compiler runtime or libc.
|
||||
type Library struct {
|
||||
// The library name, such as compiler-rt or picolibc.
|
||||
name string
|
||||
|
||||
cflags func() []string
|
||||
|
||||
// The source directory, relative to TINYGOROOT.
|
||||
sourceDir string
|
||||
|
||||
// The source files, relative to sourceDir.
|
||||
sources 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if job != nil {
|
||||
jobs := append([]*compileJob{job}, job.dependencies...)
|
||||
err = runJobs(jobs)
|
||||
}
|
||||
return path, 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.
|
||||
// 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 {
|
||||
// Found a precompiled library for this OS/architecture. Return the path
|
||||
// directly.
|
||||
return precompiledPath, nil, nil
|
||||
}
|
||||
|
||||
var outfile string
|
||||
if cpu != "" {
|
||||
outfile = l.name + "-" + target + "-" + cpu + ".a"
|
||||
} else {
|
||||
outfile = l.name + "-" + target + ".a"
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
// Cache miss, build it now.
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
if cpu != "" {
|
||||
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")
|
||||
}
|
||||
if strings.HasPrefix(target, "riscv32-") {
|
||||
args = append(args, "-march=rv32imac", "-mabi=ilp32", "-fforce-enable-int128")
|
||||
}
|
||||
if strings.HasPrefix(target, "riscv64-") {
|
||||
args = append(args, "-march=rv64gc", "-mabi=lp64")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// Create an archive of all object files.
|
||||
err := makeArchive(arpath, objs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Store this archive in the cache.
|
||||
_, err = cacheStore(arpath, outfile, l.sourcePaths(target))
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
// 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")
|
||||
objs = append(objs, objpath)
|
||||
job.dependencies = append(job.dependencies, &compileJob{
|
||||
description: "compile " + srcpath,
|
||||
run: func() error {
|
||||
var compileArgs []string
|
||||
compileArgs = append(compileArgs, args...)
|
||||
compileArgs = append(compileArgs, "-o", objpath, srcpath)
|
||||
err := runCCompiler("clang", compileArgs...)
|
||||
if err != nil {
|
||||
return &commandError{"failed to build", srcpath, err}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return arpath, job, nil
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
// +build byollvm
|
||||
|
||||
package builder
|
||||
|
||||
// This file provides a Link() function that uses the bundled lld if possible.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"unsafe"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
/*
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
bool tinygo_link_elf(int argc, char **argv);
|
||||
bool tinygo_link_wasm(int argc, char **argv);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
// link invokes a linker with the given name and flags.
|
||||
//
|
||||
// This version uses the built-in linker when trying to use lld.
|
||||
func link(linker string, flags ...string) error {
|
||||
switch linker {
|
||||
case "ld.lld":
|
||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
|
||||
for i, flag := range flags {
|
||||
cflag := C.CString(flag)
|
||||
cflags[i] = cflag
|
||||
defer C.free(unsafe.Pointer(cflag))
|
||||
}
|
||||
ok := C.tinygo_link_elf(C.int(len(flags)), (**C.char)(buf))
|
||||
if !ok {
|
||||
return errors.New("failed to link using built-in ld.lld")
|
||||
}
|
||||
return nil
|
||||
case "wasm-ld":
|
||||
flags = append([]string{"tinygo:" + linker}, flags...)
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(flags)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
defer C.free(buf)
|
||||
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(flags):len(flags)]
|
||||
for i, flag := range flags {
|
||||
cflag := C.CString(flag)
|
||||
cflags[i] = cflag
|
||||
defer C.free(unsafe.Pointer(cflag))
|
||||
}
|
||||
ok := C.tinygo_link_wasm(C.int(len(flags)), (**C.char)(buf))
|
||||
if !ok {
|
||||
return errors.New("failed to link using built-in wasm-ld")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
// Fall back to external command.
|
||||
if cmdNames, ok := commands[linker]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = goenv.Get("TINYGOROOT")
|
||||
return cmd.Run()
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
// +build !byollvm
|
||||
|
||||
package builder
|
||||
|
||||
// This file provides a Link() function that always runs an external command. It
|
||||
// is provided for when tinygo is built without linking to liblld.
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// link invokes a linker with the given name and arguments.
|
||||
//
|
||||
// This version always runs the linker as an external command.
|
||||
func link(linker string, flags ...string) error {
|
||||
if cmdNames, ok := commands[linker]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = goenv.Get("TINYGOROOT")
|
||||
return cmd.Run()
|
||||
}
|
||||
+2
-2
@@ -8,12 +8,12 @@ extern "C" {
|
||||
|
||||
bool tinygo_link_elf(int argc, char **argv) {
|
||||
std::vector<const char*> args(argv, argv + argc);
|
||||
return lld::elf::link(args, false);
|
||||
return lld::elf::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);
|
||||
return lld::wasm::link(args, false, llvm::outs(), llvm::errs());
|
||||
}
|
||||
|
||||
} // external "C"
|
||||
|
||||
+27
-17
@@ -4,12 +4,15 @@ import (
|
||||
"debug/elf"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
||||
"github.com/marcinbor85/gohex"
|
||||
)
|
||||
|
||||
// maxPadBytes is the maximum allowed bytes to be padded in a rom extraction
|
||||
// this value is currently defined by Nintendo Switch Page Alignment (4096 bytes)
|
||||
const maxPadBytes = 4095
|
||||
|
||||
// objcopyError is an error returned by functions that act like objcopy.
|
||||
type objcopyError struct {
|
||||
Op string
|
||||
@@ -58,7 +61,7 @@ func extractROM(path string) (uint64, []byte, error) {
|
||||
|
||||
progs := make(progSlice, 0, 2)
|
||||
for _, prog := range f.Progs {
|
||||
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 {
|
||||
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 {
|
||||
continue
|
||||
}
|
||||
progs = append(progs, prog)
|
||||
@@ -70,8 +73,19 @@ func extractROM(path string) (uint64, []byte, error) {
|
||||
|
||||
var rom []byte
|
||||
for _, prog := range progs {
|
||||
romEnd := progs[0].Paddr + uint64(len(rom))
|
||||
if prog.Paddr > romEnd && prog.Paddr < romEnd+16 {
|
||||
// Sometimes, the linker seems to insert a bit of padding between
|
||||
// segments. Simply zero-fill these parts.
|
||||
rom = append(rom, make([]byte, prog.Paddr-romEnd)...)
|
||||
}
|
||||
if prog.Paddr != progs[0].Paddr+uint64(len(rom)) {
|
||||
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
|
||||
diff := prog.Paddr - (progs[0].Paddr + uint64(len(rom)))
|
||||
if diff > maxPadBytes {
|
||||
return 0, nil, objcopyError{"ROM segments are non-contiguous: " + path, nil}
|
||||
}
|
||||
// Pad the difference
|
||||
rom = append(rom, make([]byte, diff)...)
|
||||
}
|
||||
data, err := ioutil.ReadAll(prog.Open())
|
||||
if err != nil {
|
||||
@@ -93,7 +107,7 @@ func extractROM(path string) (uint64, []byte, error) {
|
||||
|
||||
// objcopy converts an ELF file to a different (simpler) output file format:
|
||||
// .bin or .hex. It extracts only the .text section.
|
||||
func objcopy(infile, outfile string) error {
|
||||
func objcopy(infile, outfile, binaryFormat string) error {
|
||||
f, err := os.OpenFile(outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -107,24 +121,20 @@ func objcopy(infile, outfile string) error {
|
||||
}
|
||||
|
||||
// Write to the file, in the correct format.
|
||||
switch filepath.Ext(outfile) {
|
||||
case ".gba":
|
||||
// The address is not stored in a .gba file.
|
||||
_, err := f.Write(data)
|
||||
return err
|
||||
case ".bin":
|
||||
// The address is not stored in a .bin file (therefore you
|
||||
// should use .hex files in most cases).
|
||||
_, err := f.Write(data)
|
||||
return err
|
||||
case ".hex":
|
||||
switch binaryFormat {
|
||||
case "hex":
|
||||
// Intel hex file, includes the firmware start address.
|
||||
mem := gohex.NewMemory()
|
||||
err := mem.AddBinary(uint32(addr), data)
|
||||
if err != nil {
|
||||
return objcopyError{"failed to create .hex file", err}
|
||||
}
|
||||
mem.DumpIntelHex(f, 16) // TODO: handle error
|
||||
return nil
|
||||
return mem.DumpIntelHex(f, 16)
|
||||
case "bin":
|
||||
// The start address is not stored in raw firmware files (therefore you
|
||||
// should use .hex files in most cases).
|
||||
_, err := f.Write(data)
|
||||
return err
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// Picolibc is a C library for bare metal embedded devices. It was originally
|
||||
// based on newlib.
|
||||
var Picolibc = Library{
|
||||
name: "picolibc",
|
||||
cflags: func() []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"}
|
||||
},
|
||||
sourceDir: "lib/picolibc/newlib/libc",
|
||||
sources: func(target string) []string {
|
||||
return picolibcSources
|
||||
},
|
||||
}
|
||||
|
||||
var picolibcSources = []string{
|
||||
"string/bcmp.c",
|
||||
"string/bcopy.c",
|
||||
"string/bzero.c",
|
||||
"string/explicit_bzero.c",
|
||||
"string/ffsl.c",
|
||||
"string/ffsll.c",
|
||||
"string/fls.c",
|
||||
"string/flsl.c",
|
||||
"string/flsll.c",
|
||||
"string/gnu_basename.c",
|
||||
"string/index.c",
|
||||
"string/memccpy.c",
|
||||
"string/memchr.c",
|
||||
"string/memcmp.c",
|
||||
"string/memcpy.c",
|
||||
"string/memmem.c",
|
||||
"string/memmove.c",
|
||||
"string/mempcpy.c",
|
||||
"string/memrchr.c",
|
||||
"string/memset.c",
|
||||
"string/rawmemchr.c",
|
||||
"string/rindex.c",
|
||||
"string/stpcpy.c",
|
||||
"string/stpncpy.c",
|
||||
"string/strcasecmp.c",
|
||||
"string/strcasecmp_l.c",
|
||||
"string/strcasestr.c",
|
||||
"string/strcat.c",
|
||||
"string/strchr.c",
|
||||
"string/strchrnul.c",
|
||||
"string/strcmp.c",
|
||||
"string/strcoll.c",
|
||||
"string/strcoll_l.c",
|
||||
"string/strcpy.c",
|
||||
"string/strcspn.c",
|
||||
"string/strdup.c",
|
||||
"string/strerror.c",
|
||||
"string/strerror_r.c",
|
||||
"string/strlcat.c",
|
||||
"string/strlcpy.c",
|
||||
"string/strlen.c",
|
||||
"string/strlwr.c",
|
||||
"string/strncasecmp.c",
|
||||
"string/strncasecmp_l.c",
|
||||
"string/strncat.c",
|
||||
"string/strncmp.c",
|
||||
"string/strncpy.c",
|
||||
"string/strndup.c",
|
||||
"string/strnlen.c",
|
||||
"string/strnstr.c",
|
||||
"string/strpbrk.c",
|
||||
"string/strrchr.c",
|
||||
"string/strsep.c",
|
||||
"string/strsignal.c",
|
||||
"string/strspn.c",
|
||||
"string/strstr.c",
|
||||
"string/strtok.c",
|
||||
"string/strtok_r.c",
|
||||
"string/strupr.c",
|
||||
"string/strverscmp.c",
|
||||
"string/strxfrm.c",
|
||||
"string/strxfrm_l.c",
|
||||
"string/swab.c",
|
||||
"string/timingsafe_bcmp.c",
|
||||
"string/timingsafe_memcmp.c",
|
||||
"string/u_strerr.c",
|
||||
"string/wcpcpy.c",
|
||||
"string/wcpncpy.c",
|
||||
"string/wcscasecmp.c",
|
||||
"string/wcscasecmp_l.c",
|
||||
"string/wcscat.c",
|
||||
"string/wcschr.c",
|
||||
"string/wcscmp.c",
|
||||
"string/wcscoll.c",
|
||||
"string/wcscoll_l.c",
|
||||
"string/wcscpy.c",
|
||||
"string/wcscspn.c",
|
||||
"string/wcsdup.c",
|
||||
"string/wcslcat.c",
|
||||
"string/wcslcpy.c",
|
||||
"string/wcslen.c",
|
||||
"string/wcsncasecmp.c",
|
||||
"string/wcsncasecmp_l.c",
|
||||
"string/wcsncat.c",
|
||||
"string/wcsncmp.c",
|
||||
"string/wcsncpy.c",
|
||||
"string/wcsnlen.c",
|
||||
"string/wcspbrk.c",
|
||||
"string/wcsrchr.c",
|
||||
"string/wcsspn.c",
|
||||
"string/wcsstr.c",
|
||||
"string/wcstok.c",
|
||||
"string/wcswidth.c",
|
||||
"string/wcsxfrm.c",
|
||||
"string/wcsxfrm_l.c",
|
||||
"string/wcwidth.c",
|
||||
"string/wmemchr.c",
|
||||
"string/wmemcmp.c",
|
||||
"string/wmemcpy.c",
|
||||
"string/wmemmove.c",
|
||||
"string/wmempcpy.c",
|
||||
"string/wmemset.c",
|
||||
"string/xpg_strerror_r.c",
|
||||
}
|
||||
+12
-1
@@ -84,7 +84,18 @@ func loadProgramSize(path string) (*programSize, error) {
|
||||
if section.Type != elf.SHT_PROGBITS && section.Type != elf.SHT_NOBITS {
|
||||
continue
|
||||
}
|
||||
if section.Type == elf.SHT_NOBITS {
|
||||
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
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// +build byollvm
|
||||
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
/*
|
||||
#cgo CXXFLAGS: -fno-rtti
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
bool tinygo_clang_driver(int argc, char **argv);
|
||||
bool tinygo_link_elf(int argc, char **argv);
|
||||
bool tinygo_link_wasm(int argc, char **argv);
|
||||
*/
|
||||
import "C"
|
||||
|
||||
const hasBuiltinTools = true
|
||||
|
||||
// RunTool runs the given tool (such as clang).
|
||||
//
|
||||
// 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 {
|
||||
args = append([]string{"tinygo:" + tool}, args...)
|
||||
|
||||
var cflag *C.char
|
||||
buf := C.calloc(C.size_t(len(args)), C.size_t(unsafe.Sizeof(cflag)))
|
||||
defer C.free(buf)
|
||||
cflags := (*[1 << 10]*C.char)(unsafe.Pointer(buf))[:len(args):len(args)]
|
||||
for i, flag := range args {
|
||||
cflag := C.CString(flag)
|
||||
cflags[i] = cflag
|
||||
defer C.free(unsafe.Pointer(cflag))
|
||||
}
|
||||
|
||||
var ok C.bool
|
||||
switch tool {
|
||||
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))
|
||||
case "wasm-ld":
|
||||
ok = C.tinygo_link_wasm(C.int(len(args)), (**C.char)(buf))
|
||||
default:
|
||||
return errors.New("unknown tool: " + tool)
|
||||
}
|
||||
if !ok {
|
||||
return errors.New("failed to run tool: " + tool)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// +build !byollvm
|
||||
|
||||
package builder
|
||||
|
||||
import "errors"
|
||||
|
||||
const hasBuiltinTools = false
|
||||
|
||||
// RunTool runs the given tool (such as clang).
|
||||
//
|
||||
// This version doesn't actually run the tool: TinyGo has not been compiled by
|
||||
// statically linking to LLVM.
|
||||
func RunTool(tool string, args ...string) error {
|
||||
return errors.New("cannot run tool: " + tool)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
)
|
||||
|
||||
// runCCompiler invokes a C compiler with the given arguments.
|
||||
func runCCompiler(command string, flags ...string) error {
|
||||
if hasBuiltinTools && command == "clang" {
|
||||
// Compile this with the internal Clang compiler.
|
||||
headerPath := getClangHeaderPath(goenv.Get("TINYGOROOT"))
|
||||
if headerPath == "" {
|
||||
return errors.New("could not locate Clang headers")
|
||||
}
|
||||
flags = append(flags, "-I"+headerPath)
|
||||
cmd := exec.Command(os.Args[0], append([]string{"clang"}, flags...)...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
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()
|
||||
}
|
||||
|
||||
// link invokes a linker with the given name and flags.
|
||||
func link(linker string, flags ...string) error {
|
||||
if hasBuiltinTools && (linker == "ld.lld" || linker == "wasm-ld") {
|
||||
// Run command with internal linker.
|
||||
cmd := exec.Command(os.Args[0], append([]string{linker}, flags...)...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// Fall back to external command.
|
||||
if cmdNames, ok := commands[linker]; ok {
|
||||
return execCommand(cmdNames, flags...)
|
||||
}
|
||||
|
||||
cmd := exec.Command(linker, flags...)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Dir = goenv.Get("TINYGOROOT")
|
||||
return cmd.Run()
|
||||
}
|
||||
+74
-5
@@ -14,7 +14,9 @@ package cgo
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -39,6 +41,7 @@ type cgoPackage struct {
|
||||
elaboratedTypes map[string]*elaboratedTypeInfo
|
||||
enums map[string]enumInfo
|
||||
anonStructNum int
|
||||
ldflags []string
|
||||
}
|
||||
|
||||
// constantInfo stores some information about a CGo constant found by libclang
|
||||
@@ -51,9 +54,10 @@ type constantInfo struct {
|
||||
// functionInfo stores some information about a CGo function found by libclang
|
||||
// and declared in the AST.
|
||||
type functionInfo struct {
|
||||
args []paramInfo
|
||||
results *ast.FieldList
|
||||
pos token.Pos
|
||||
args []paramInfo
|
||||
results *ast.FieldList
|
||||
pos token.Pos
|
||||
variadic bool
|
||||
}
|
||||
|
||||
// paramInfo is a parameter of a CGo function (see functionInfo).
|
||||
@@ -154,7 +158,7 @@ typedef unsigned long long _Cgo_ulonglong;
|
||||
// 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, []error) {
|
||||
func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string) (*ast.File, []string, []error) {
|
||||
p := &cgoPackage{
|
||||
dir: dir,
|
||||
fset: fset,
|
||||
@@ -168,11 +172,28 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
enums: map[string]enumInfo{},
|
||||
}
|
||||
|
||||
// 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})
|
||||
p.generatedPos = generatedTokenPos.Pos(0)
|
||||
|
||||
// Find the absolute path for this package.
|
||||
packagePath, err := filepath.Abs(fset.File(files[0].Pos()).Name())
|
||||
if err != nil {
|
||||
return 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)
|
||||
|
||||
// Construct a new in-memory AST for CGo declarations of this package.
|
||||
unsafeImport := &ast.ImportSpec{
|
||||
Path: &ast.BasicLit{
|
||||
@@ -338,7 +359,21 @@ func Process(files []*ast.File, dir string, fset *token.FileSet, cflags []string
|
||||
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)
|
||||
@@ -392,7 +427,31 @@ 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.errors
|
||||
return p.generated, p.ldflags, 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) {
|
||||
nextIsPath := false
|
||||
for i, arg := range args {
|
||||
if nextIsPath {
|
||||
if !filepath.IsAbs(arg) {
|
||||
args[i] = filepath.Join(packagePath, arg)
|
||||
}
|
||||
}
|
||||
if arg == "-I" || arg == "-L" {
|
||||
nextIsPath = true
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(arg, "-I") || strings.HasPrefix(arg, "-L") {
|
||||
path := arg[2:]
|
||||
if !filepath.IsAbs(path) {
|
||||
args[i] = arg[:2] + filepath.Join(packagePath, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addFuncDecls adds the C function declarations found by libclang in the
|
||||
@@ -426,6 +485,16 @@ func (p *cgoPackage) addFuncDecls() {
|
||||
Results: fn.results,
|
||||
},
|
||||
}
|
||||
if fn.variadic {
|
||||
decl.Doc = &ast.CommentGroup{
|
||||
List: []*ast.Comment{
|
||||
&ast.Comment{
|
||||
Slash: fn.pos,
|
||||
Text: "//go:variadic",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
obj.Decl = decl
|
||||
for i, arg := range fn.args {
|
||||
args[i] = &ast.Field{
|
||||
|
||||
+33
-2
@@ -5,12 +5,14 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/build"
|
||||
"go/format"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -19,12 +21,41 @@ import (
|
||||
// Pass -update to go test to update the output of the test files.
|
||||
var flagUpdate = flag.Bool("update", false, "Update images based on test output.")
|
||||
|
||||
// normalizeResult normalizes Go source code that comes out of tests across
|
||||
// platforms and Go versions.
|
||||
func normalizeResult(result string) string {
|
||||
actual := strings.Replace(result, "\r\n", "\n", -1)
|
||||
|
||||
// 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"}
|
||||
|
||||
for _, name := range []string{"basic", "errors", "types", "flags", "const"} {
|
||||
name := name // avoid a race condition
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// Skip tests that require specific Go version.
|
||||
if name == "errors" {
|
||||
ok := false
|
||||
for _, version := range build.Default.ReleaseTags {
|
||||
if version == "go1.16" {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
t.Skip("Results for errors test are only valid for Go 1.16+")
|
||||
}
|
||||
}
|
||||
|
||||
// Read the AST in memory.
|
||||
path := filepath.Join("testdata", name+".go")
|
||||
fset := token.NewFileSet()
|
||||
@@ -34,7 +65,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
|
||||
@@ -74,7 +105,7 @@ func TestCGo(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("could not write out CGo AST: %v", err)
|
||||
}
|
||||
actual := strings.Replace(string(buf.Bytes()), "\r\n", "\n", -1)
|
||||
actual := normalizeResult(string(buf.Bytes()))
|
||||
|
||||
// Read the file with the expected output, to compare against.
|
||||
outfile := filepath.Join("testdata", name+".out.go")
|
||||
|
||||
+6
-8
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
/*
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-9-dev
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -152,12 +152,10 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
||||
return C.CXChildVisit_Continue
|
||||
}
|
||||
cursorType := C.tinygo_clang_getCursorType(c)
|
||||
if C.clang_isFunctionTypeVariadic(cursorType) != 0 {
|
||||
return C.CXChildVisit_Continue // not supported
|
||||
}
|
||||
numArgs := int(C.tinygo_clang_Cursor_getNumArguments(c))
|
||||
fn := &functionInfo{
|
||||
pos: pos,
|
||||
pos: pos,
|
||||
variadic: C.clang_isFunctionTypeVariadic(cursorType) != 0,
|
||||
}
|
||||
p.functions[name] = fn
|
||||
for i := 0; i < numArgs; i++ {
|
||||
@@ -246,9 +244,9 @@ func tinygo_clang_globals_visitor(c, parent C.GoCXCursor, client_data C.CXClient
|
||||
}
|
||||
value := source[len(name):]
|
||||
// Try to convert this #define into a Go constant expression.
|
||||
expr, err := parseConst(pos+token.Pos(len(name)), p.fset, value)
|
||||
if err != nil {
|
||||
p.errors = append(p.errors, err)
|
||||
expr, scannerError := parseConst(pos+token.Pos(len(name)), p.fset, value)
|
||||
if scannerError != nil {
|
||||
p.errors = append(p.errors, *scannerError)
|
||||
}
|
||||
if expr != nil {
|
||||
// Parsing was successful.
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
// +build !byollvm
|
||||
// +build !llvm10
|
||||
|
||||
package cgo
|
||||
|
||||
/*
|
||||
#cgo linux CFLAGS: -I/usr/lib/llvm-9/include
|
||||
#cgo darwin CFLAGS: -I/usr/local/opt/llvm@9/include
|
||||
#cgo freebsd CFLAGS: -I/usr/local/llvm90/include
|
||||
#cgo linux LDFLAGS: -L/usr/lib/llvm-9/lib -lclang
|
||||
#cgo darwin LDFLAGS: -L/usr/local/opt/llvm@9/lib -lclang -lffi
|
||||
#cgo freebsd LDFLAGS: -L/usr/local/llvm90/lib -lclang
|
||||
#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"
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// +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"
|
||||
@@ -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-9-dev
|
||||
#include <clang-c/Index.h> // if this fails, install libclang-10-dev
|
||||
|
||||
CXCursor tinygo_clang_getTranslationUnitCursor(CXTranslationUnit tu) {
|
||||
return clang_getTranslationUnitCursor(tu);
|
||||
|
||||
Vendored
+2
-2
@@ -4,10 +4,10 @@
|
||||
// testdata/errors.go:13:23: unexpected token )
|
||||
|
||||
// Type checking errors after CGo processing:
|
||||
// testdata/errors.go:102: 2 << 10 (untyped int constant 2048) overflows uint8
|
||||
// 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: C.SOME_CONST_3 (untyped int constant 1234) overflows byte
|
||||
// testdata/errors.go:110: cannot use C.SOME_CONST_3 (untyped int constant 1234) as byte value in variable declaration (overflows)
|
||||
|
||||
package main
|
||||
|
||||
|
||||
Vendored
+11
@@ -9,6 +9,9 @@ package main
|
||||
|
||||
#cgo CFLAGS: -DFOO
|
||||
|
||||
#cgo CFLAGS: -Iinclude
|
||||
#include "foo.h"
|
||||
|
||||
#if defined(FOO)
|
||||
#define BAR 3
|
||||
#else
|
||||
@@ -18,9 +21,17 @@ package main
|
||||
#if defined(NOTDEFINED)
|
||||
#warning flag must not be defined
|
||||
#endif
|
||||
|
||||
// Check Compiler flags
|
||||
#cgo LDFLAGS: -lc
|
||||
|
||||
// This flag is not valid ldflags
|
||||
#cgo LDFLAGS: -does-not-exists
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
var (
|
||||
_ = C.BAR
|
||||
_ = C.FOO_H
|
||||
)
|
||||
|
||||
Vendored
+2
@@ -1,6 +1,7 @@
|
||||
// CGo errors:
|
||||
// testdata/flags.go:5:7: invalid #cgo line: NOFLAGS
|
||||
// testdata/flags.go:8:13: invalid flag: -fdoes-not-exist
|
||||
// testdata/flags.go:29:14: invalid flag: -does-not-exists
|
||||
|
||||
package main
|
||||
|
||||
@@ -9,6 +10,7 @@ import "unsafe"
|
||||
var _ unsafe.Pointer
|
||||
|
||||
const C.BAR = 3
|
||||
const C.FOO_H = 1
|
||||
|
||||
type C.int16_t = int16
|
||||
type C.int32_t = int32
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
#define FOO_H 1
|
||||
Vendored
+10
@@ -104,6 +104,10 @@ 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"
|
||||
|
||||
@@ -163,3 +167,9 @@ 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
+44
-13
@@ -4,6 +4,11 @@ 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
|
||||
|
||||
const C.option2A = 20
|
||||
const C.optionA = 0
|
||||
const C.optionB = 1
|
||||
@@ -62,12 +67,18 @@ 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) set_bitfield_a(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x1f | value&0x1f<<0 }
|
||||
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
|
||||
}
|
||||
func (s *C.struct_4) bitfield_b() C.uchar {
|
||||
return s.__bitfield_1 >> 5 & 0x1
|
||||
}
|
||||
func (s *C.struct_4) set_bitfield_b(value C.uchar) { s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5 }
|
||||
func (s *C.struct_4) set_bitfield_b(value C.uchar) {
|
||||
s.__bitfield_1 = s.__bitfield_1&^0x20 | value&0x1<<5
|
||||
}
|
||||
func (s *C.struct_4) bitfield_c() C.uchar {
|
||||
return s.__bitfield_1 >> 6
|
||||
}
|
||||
@@ -94,25 +105,45 @@ 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 }
|
||||
|
||||
func (union *C.union_2) unionfield_area() *C.point2d_t { return (*C.point2d_t)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_2) unionfield_solid() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_2) unionfield_area() *C.point2d_t {
|
||||
return (*C.point2d_t)(unsafe.Pointer(&union.$union))
|
||||
}
|
||||
func (union *C.union_2) unionfield_solid() *C.point3d_t {
|
||||
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
|
||||
}
|
||||
|
||||
type C.union_2 struct{ $union [3]uint32 }
|
||||
|
||||
func (union *C.union_3) unionfield_point() *C.point3d_t { return (*C.point3d_t)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_3) unionfield_array() *C.unionarray_t { return (*C.unionarray_t)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_3) unionfield_thing() *C.union3_t { return (*C.union3_t)(unsafe.Pointer(&union.$union)) }
|
||||
func (union *C.union_3) unionfield_point() *C.point3d_t {
|
||||
return (*C.point3d_t)(unsafe.Pointer(&union.$union))
|
||||
}
|
||||
func (union *C.union_3) unionfield_array() *C.unionarray_t {
|
||||
return (*C.unionarray_t)(unsafe.Pointer(&union.$union))
|
||||
}
|
||||
func (union *C.union_3) unionfield_thing() *C.union3_t {
|
||||
return (*C.union3_t)(unsafe.Pointer(&union.$union))
|
||||
}
|
||||
|
||||
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_d() *[2]float64 { return (*[2]float64)(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))
|
||||
}
|
||||
|
||||
type C.union_union2d struct{ $union [2]uint64 }
|
||||
type C.enum_option C.int
|
||||
|
||||
+119
-19
@@ -5,8 +5,8 @@ package compileopts
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/goenv"
|
||||
@@ -72,7 +72,7 @@ func (c *Config) CgoEnabled() bool {
|
||||
}
|
||||
|
||||
// GC returns the garbage collection strategy in use on this platform. Valid
|
||||
// values are "none", "leaking", and "conservative".
|
||||
// values are "none", "leaking", "extalloc", and "conservative".
|
||||
func (c *Config) GC() string {
|
||||
if c.Options.GC != "" {
|
||||
return c.Options.GC
|
||||
@@ -80,26 +80,33 @@ func (c *Config) GC() string {
|
||||
if c.Target.GC != "" {
|
||||
return c.Target.GC
|
||||
}
|
||||
return "conservative"
|
||||
for _, tag := range c.Target.BuildTags {
|
||||
if tag == "baremetal" || tag == "wasm" {
|
||||
return "conservative"
|
||||
}
|
||||
}
|
||||
return "extalloc"
|
||||
}
|
||||
|
||||
// NeedsStackObjects returns true if the compiler should insert stack objects
|
||||
// that can be traced by the garbage collector.
|
||||
func (c *Config) NeedsStackObjects() bool {
|
||||
if c.GC() != "conservative" {
|
||||
switch c.GC() {
|
||||
case "conservative", "extalloc":
|
||||
for _, tag := range c.BuildTags() {
|
||||
if tag == "wasm" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
default:
|
||||
return false
|
||||
}
|
||||
for _, tag := range c.BuildTags() {
|
||||
if tag == "baremetal" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Scheduler returns the scheduler implementation. Valid values are "coroutines"
|
||||
// and "tasks".
|
||||
// Scheduler returns the scheduler implementation. Valid values are "none",
|
||||
//"coroutines" and "tasks".
|
||||
func (c *Config) Scheduler() string {
|
||||
if c.Options.Scheduler != "" {
|
||||
return c.Options.Scheduler
|
||||
@@ -111,6 +118,31 @@ func (c *Config) Scheduler() string {
|
||||
return "coroutines"
|
||||
}
|
||||
|
||||
// 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"
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
}
|
||||
|
||||
// PanicStrategy returns the panic strategy selected for this target. Valid
|
||||
// values are "print" (print the panic value, then exit) or "trap" (issue a trap
|
||||
// instruction).
|
||||
@@ -118,6 +150,16 @@ func (c *Config) PanicStrategy() string {
|
||||
return c.Options.PanicStrategy
|
||||
}
|
||||
|
||||
// AutomaticStackSize returns whether goroutine stack sizes should be determined
|
||||
// automatically at compile time, if possible. If it is false, no attempt is
|
||||
// made.
|
||||
func (c *Config) AutomaticStackSize() bool {
|
||||
if c.Target.AutoStackSize != nil && c.Scheduler() == "tasks" {
|
||||
return *c.Target.AutoStackSize
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CFlags returns the flags to pass to the C compiler. This is necessary for CGo
|
||||
// preprocessing.
|
||||
func (c *Config) CFlags() []string {
|
||||
@@ -125,6 +167,14 @@ func (c *Config) CFlags() []string {
|
||||
for _, flag := range c.Target.CFlags {
|
||||
cflags = append(cflags, strings.Replace(flag, "{root}", goenv.Get("TINYGOROOT"), -1))
|
||||
}
|
||||
if c.Target.Libc == "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"))
|
||||
}
|
||||
if c.Debug() {
|
||||
cflags = append(cflags, "-g")
|
||||
}
|
||||
return cflags
|
||||
}
|
||||
|
||||
@@ -139,12 +189,6 @@ func (c *Config) LDFlags() []string {
|
||||
ldflags = append(ldflags, strings.Replace(flag, "{root}", root, -1))
|
||||
}
|
||||
ldflags = append(ldflags, "-L", root)
|
||||
if c.Target.GOARCH == "wasm" {
|
||||
// Round heap size to next multiple of 65536 (the WebAssembly page
|
||||
// size).
|
||||
heapSize := (c.Options.HeapSize + (65536 - 1)) &^ (65536 - 1)
|
||||
ldflags = append(ldflags, "--initial-memory="+strconv.FormatInt(heapSize, 10))
|
||||
}
|
||||
if c.Target.LinkerScript != "" {
|
||||
ldflags = append(ldflags, "-T", c.Target.LinkerScript)
|
||||
}
|
||||
@@ -175,6 +219,31 @@ func (c *Config) Debug() bool {
|
||||
return c.Options.Debug
|
||||
}
|
||||
|
||||
// BinaryFormat returns an appropriate binary format, based on the file
|
||||
// extension and the configured binary format in the target JSON file.
|
||||
func (c *Config) BinaryFormat(ext string) string {
|
||||
switch ext {
|
||||
case ".bin", ".gba", ".nro":
|
||||
// The simplest format possible: dump everything in a raw binary file.
|
||||
if c.Target.BinaryFormat != "" {
|
||||
return c.Target.BinaryFormat
|
||||
}
|
||||
return "bin"
|
||||
case ".hex":
|
||||
// Similar to bin, but includes the start address and is thus usually a
|
||||
// better format.
|
||||
return "hex"
|
||||
case ".uf2":
|
||||
// Special purpose firmware format, mainly used on Adafruit boards.
|
||||
// More information:
|
||||
// https://github.com/Microsoft/uf2
|
||||
return "uf2"
|
||||
default:
|
||||
// Use the ELF format for unrecognized file formats.
|
||||
return "elf"
|
||||
}
|
||||
}
|
||||
|
||||
// Programmer returns the flash method and OpenOCD interface name given a
|
||||
// particular configuration. It may either be all configured in the target JSON
|
||||
// file or be modified using the -programmmer command-line option.
|
||||
@@ -214,6 +283,9 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
|
||||
return nil, fmt.Errorf("unknown OpenOCD transport: %#v", c.Target.OpenOCDTransport)
|
||||
}
|
||||
args = []string{"-f", "interface/" + openocdInterface + ".cfg"}
|
||||
for _, cmd := range c.Target.OpenOCDCommands {
|
||||
args = append(args, "-c", cmd)
|
||||
}
|
||||
if c.Target.OpenOCDTransport != "" {
|
||||
args = append(args, "-c", "transport select "+c.Target.OpenOCDTransport)
|
||||
}
|
||||
@@ -221,6 +293,34 @@ func (c *Config) OpenOCDConfiguration() (args []string, err error) {
|
||||
return args, nil
|
||||
}
|
||||
|
||||
// CodeModel returns the code model used on this platform.
|
||||
func (c *Config) CodeModel() string {
|
||||
if c.Target.CodeModel != "" {
|
||||
return c.Target.CodeModel
|
||||
}
|
||||
|
||||
return "default"
|
||||
}
|
||||
|
||||
// RelocationModel returns the relocation model in use on this platform. Valid
|
||||
// values are "static", "pic", "dynamicnopic".
|
||||
func (c *Config) RelocationModel() string {
|
||||
if c.Target.RelocationModel != "" {
|
||||
return c.Target.RelocationModel
|
||||
}
|
||||
|
||||
return "static"
|
||||
}
|
||||
|
||||
// WasmAbi returns the WASM ABI which is specified in the target JSON file, and
|
||||
// the value is overridden by `-wasm-abi` flag if it is provided
|
||||
func (c *Config) WasmAbi() string {
|
||||
if c.Options.WasmAbi != "" {
|
||||
return c.Options.WasmAbi
|
||||
}
|
||||
return c.Target.WasmAbi
|
||||
}
|
||||
|
||||
type TestConfig struct {
|
||||
CompileTestBinary bool
|
||||
// TODO: Filter the test functions to run, include verbose flag, etc
|
||||
|
||||
+64
-1
@@ -1,5 +1,17 @@
|
||||
package compileopts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
validGCOptions = []string{"none", "leaking", "extalloc", "conservative"}
|
||||
validSchedulerOptions = []string{"none", "tasks", "coroutines"}
|
||||
validPrintSizeOptions = []string{"none", "short", "full"}
|
||||
validPanicStrategyOptions = []string{"print", "trap"}
|
||||
)
|
||||
|
||||
// Options contains extra options to give to the compiler. These options are
|
||||
// usually passed from the command line.
|
||||
type Options struct {
|
||||
@@ -11,13 +23,64 @@ type Options struct {
|
||||
PrintIR bool
|
||||
DumpSSA bool
|
||||
VerifyIR bool
|
||||
PrintCommands bool
|
||||
Debug bool
|
||||
PrintSizes string
|
||||
PrintStacks bool
|
||||
CFlags []string
|
||||
LDFlags []string
|
||||
Tags string
|
||||
WasmAbi string
|
||||
HeapSize int64
|
||||
TestConfig TestConfig
|
||||
Programmer string
|
||||
}
|
||||
|
||||
// Verify performs a validation on the given options, raising an error if options are not valid.
|
||||
func (o *Options) Verify() error {
|
||||
if o.GC != "" {
|
||||
valid := isInArray(validGCOptions, o.GC)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid gc option '%s': valid values are %s`,
|
||||
o.GC,
|
||||
strings.Join(validGCOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
if o.Scheduler != "" {
|
||||
valid := isInArray(validSchedulerOptions, o.Scheduler)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid scheduler option '%s': valid values are %s`,
|
||||
o.Scheduler,
|
||||
strings.Join(validSchedulerOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
if o.PrintSizes != "" {
|
||||
valid := isInArray(validPrintSizeOptions, o.PrintSizes)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid size option '%s': valid values are %s`,
|
||||
o.PrintSizes,
|
||||
strings.Join(validPrintSizeOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
if o.PanicStrategy != "" {
|
||||
valid := isInArray(validPanicStrategyOptions, o.PanicStrategy)
|
||||
if !valid {
|
||||
return fmt.Errorf(`invalid panic option '%s': valid values are %s`,
|
||||
o.PanicStrategy,
|
||||
strings.Join(validPanicStrategyOptions, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isInArray(arr []string, item string) bool {
|
||||
for _, i := range arr {
|
||||
if i == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
package compileopts_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
)
|
||||
|
||||
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`)
|
||||
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`)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
opts compileopts.Options
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "OptionsEmpty",
|
||||
opts: compileopts.Options{},
|
||||
},
|
||||
{
|
||||
name: "InvalidGCOption",
|
||||
opts: compileopts.Options{
|
||||
GC: "incorrect",
|
||||
},
|
||||
expectedError: expectedGCError,
|
||||
},
|
||||
{
|
||||
name: "GCOptionNone",
|
||||
opts: compileopts.Options{
|
||||
GC: "none",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GCOptionLeaking",
|
||||
opts: compileopts.Options{
|
||||
GC: "leaking",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GCOptionExtalloc",
|
||||
opts: compileopts.Options{
|
||||
GC: "extalloc",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GCOptionConservative",
|
||||
opts: compileopts.Options{
|
||||
GC: "conservative",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "InvalidSchedulerOption",
|
||||
opts: compileopts.Options{
|
||||
Scheduler: "incorrect",
|
||||
},
|
||||
expectedError: expectedSchedulerError,
|
||||
},
|
||||
{
|
||||
name: "SchedulerOptionNone",
|
||||
opts: compileopts.Options{
|
||||
Scheduler: "none",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SchedulerOptionTasks",
|
||||
opts: compileopts.Options{
|
||||
Scheduler: "tasks",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SchedulerOptionCoroutines",
|
||||
opts: compileopts.Options{
|
||||
Scheduler: "coroutines",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "InvalidPrintSizeOption",
|
||||
opts: compileopts.Options{
|
||||
PrintSizes: "incorrect",
|
||||
},
|
||||
expectedError: expectedPrintSizeError,
|
||||
},
|
||||
{
|
||||
name: "PrintSizeOptionNone",
|
||||
opts: compileopts.Options{
|
||||
PrintSizes: "none",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PrintSizeOptionShort",
|
||||
opts: compileopts.Options{
|
||||
PrintSizes: "short",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PrintSizeOptionFull",
|
||||
opts: compileopts.Options{
|
||||
PrintSizes: "full",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "InvalidPanicOption",
|
||||
opts: compileopts.Options{
|
||||
PanicStrategy: "incorrect",
|
||||
},
|
||||
expectedError: expectedPanicStrategyError,
|
||||
},
|
||||
{
|
||||
name: "PanicOptionPrint",
|
||||
opts: compileopts.Options{
|
||||
PanicStrategy: "print",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "PanicOptionTrap",
|
||||
opts: compileopts.Options{
|
||||
PanicStrategy: "trap",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.opts.Verify()
|
||||
if tc.expectedError != err {
|
||||
if tc.expectedError.Error() != err.Error() {
|
||||
t.Errorf("expected %v, got %v", tc.expectedError, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+69
-93
@@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
@@ -32,11 +33,14 @@ type TargetSpec struct {
|
||||
Compiler string `json:"compiler"`
|
||||
Linker string `json:"linker"`
|
||||
RTLib string `json:"rtlib"` // compiler runtime library (libgcc, compiler-rt)
|
||||
Libc string `json:"libc"`
|
||||
AutoStackSize *bool `json:"automatic-stack-size"` // Determine stack size automatically at compile time.
|
||||
DefaultStackSize uint64 `json:"default-stack-size"` // Default stack size if the size couldn't be determined at compile time.
|
||||
CFlags []string `json:"cflags"`
|
||||
LDFlags []string `json:"ldflags"`
|
||||
LinkerScript string `json:"linkerscript"`
|
||||
ExtraFiles []string `json:"extra-files"`
|
||||
Emulator []string `json:"emulator"`
|
||||
Emulator []string `json:"emulator" override:"copy"` // inherited Emulator must not be append
|
||||
FlashCommand string `json:"flash-command"`
|
||||
GDB string `json:"gdb"`
|
||||
PortReset string `json:"flash-1200-bps-reset"`
|
||||
@@ -44,87 +48,57 @@ type TargetSpec struct {
|
||||
FlashVolume string `json:"msd-volume-name"`
|
||||
FlashFilename string `json:"msd-firmware-name"`
|
||||
UF2FamilyID string `json:"uf2-family-id"`
|
||||
BinaryFormat string `json:"binary-format"`
|
||||
OpenOCDInterface string `json:"openocd-interface"`
|
||||
OpenOCDTarget string `json:"openocd-target"`
|
||||
OpenOCDTransport string `json:"openocd-transport"`
|
||||
OpenOCDCommands []string `json:"openocd-commands"`
|
||||
JLinkDevice string `json:"jlink-device"`
|
||||
CodeModel string `json:"code-model"`
|
||||
RelocationModel string `json:"relocation-model"`
|
||||
WasmAbi string `json:"wasm-abi"`
|
||||
}
|
||||
|
||||
// copyProperties copies all properties that are set in spec2 into itself.
|
||||
func (spec *TargetSpec) copyProperties(spec2 *TargetSpec) {
|
||||
// TODO: simplify this using reflection? Inherits and BuildTags are special
|
||||
// cases, but the rest can simply be copied if set.
|
||||
spec.Inherits = append(spec.Inherits, spec2.Inherits...)
|
||||
if spec2.Triple != "" {
|
||||
spec.Triple = spec2.Triple
|
||||
}
|
||||
if spec2.CPU != "" {
|
||||
spec.CPU = spec2.CPU
|
||||
}
|
||||
spec.Features = append(spec.Features, spec2.Features...)
|
||||
if spec2.GOOS != "" {
|
||||
spec.GOOS = spec2.GOOS
|
||||
}
|
||||
if spec2.GOARCH != "" {
|
||||
spec.GOARCH = spec2.GOARCH
|
||||
}
|
||||
spec.BuildTags = append(spec.BuildTags, spec2.BuildTags...)
|
||||
if spec2.GC != "" {
|
||||
spec.GC = spec2.GC
|
||||
}
|
||||
if spec2.Scheduler != "" {
|
||||
spec.Scheduler = spec2.Scheduler
|
||||
}
|
||||
if spec2.Compiler != "" {
|
||||
spec.Compiler = spec2.Compiler
|
||||
}
|
||||
if spec2.Linker != "" {
|
||||
spec.Linker = spec2.Linker
|
||||
}
|
||||
if spec2.RTLib != "" {
|
||||
spec.RTLib = spec2.RTLib
|
||||
}
|
||||
spec.CFlags = append(spec.CFlags, spec2.CFlags...)
|
||||
spec.LDFlags = append(spec.LDFlags, spec2.LDFlags...)
|
||||
if spec2.LinkerScript != "" {
|
||||
spec.LinkerScript = spec2.LinkerScript
|
||||
}
|
||||
spec.ExtraFiles = append(spec.ExtraFiles, spec2.ExtraFiles...)
|
||||
if len(spec2.Emulator) != 0 {
|
||||
spec.Emulator = spec2.Emulator
|
||||
}
|
||||
if spec2.FlashCommand != "" {
|
||||
spec.FlashCommand = spec2.FlashCommand
|
||||
}
|
||||
if spec2.GDB != "" {
|
||||
spec.GDB = spec2.GDB
|
||||
}
|
||||
if spec2.PortReset != "" {
|
||||
spec.PortReset = spec2.PortReset
|
||||
}
|
||||
if spec2.FlashMethod != "" {
|
||||
spec.FlashMethod = spec2.FlashMethod
|
||||
}
|
||||
if spec2.FlashVolume != "" {
|
||||
spec.FlashVolume = spec2.FlashVolume
|
||||
}
|
||||
if spec2.FlashFilename != "" {
|
||||
spec.FlashFilename = spec2.FlashFilename
|
||||
}
|
||||
if spec2.UF2FamilyID != "" {
|
||||
spec.UF2FamilyID = spec2.UF2FamilyID
|
||||
}
|
||||
if spec2.OpenOCDInterface != "" {
|
||||
spec.OpenOCDInterface = spec2.OpenOCDInterface
|
||||
}
|
||||
if spec2.OpenOCDTarget != "" {
|
||||
spec.OpenOCDTarget = spec2.OpenOCDTarget
|
||||
}
|
||||
if spec2.OpenOCDTransport != "" {
|
||||
spec.OpenOCDTransport = spec2.OpenOCDTransport
|
||||
}
|
||||
if spec2.JLinkDevice != "" {
|
||||
spec.JLinkDevice = spec2.JLinkDevice
|
||||
// overrideProperties overrides all properties that are set in child into itself using reflection.
|
||||
func (spec *TargetSpec) overrideProperties(child *TargetSpec) {
|
||||
specType := reflect.TypeOf(spec).Elem()
|
||||
specValue := reflect.ValueOf(spec).Elem()
|
||||
childValue := reflect.ValueOf(child).Elem()
|
||||
|
||||
for i := 0; i < specType.NumField(); i++ {
|
||||
field := specType.Field(i)
|
||||
src := childValue.Field(i)
|
||||
dst := specValue.Field(i)
|
||||
|
||||
switch kind := field.Type.Kind(); kind {
|
||||
case reflect.String: // for strings, just copy the field of child to spec if not empty
|
||||
if src.Len() > 0 {
|
||||
dst.Set(src)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint32, reflect.Uint64: // for Uint, copy if not zero
|
||||
if src.Uint() != 0 {
|
||||
dst.Set(src)
|
||||
}
|
||||
case reflect.Ptr: // for pointers, copy if not nil
|
||||
if !src.IsNil() {
|
||||
dst.Set(src)
|
||||
}
|
||||
case reflect.Slice: // for slices...
|
||||
if src.Len() > 0 { // ... if not empty ...
|
||||
switch tag := field.Tag.Get("override"); tag {
|
||||
case "copy":
|
||||
// copy the field of child to spec
|
||||
dst.Set(src)
|
||||
case "append", "":
|
||||
// or append the field of child to spec
|
||||
dst.Set(reflect.AppendSlice(src, dst))
|
||||
default:
|
||||
panic("override mode must be 'copy' or 'append' (default). I don't know how to '" + tag + "'.")
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("unknown field type : " + kind.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,11 +147,11 @@ func (spec *TargetSpec) resolveInherits() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newSpec.copyProperties(subtarget)
|
||||
newSpec.overrideProperties(subtarget)
|
||||
}
|
||||
|
||||
// When all properties are loaded, make sure they are properly inherited.
|
||||
newSpec.copyProperties(spec)
|
||||
newSpec.overrideProperties(spec)
|
||||
*spec = *newSpec
|
||||
|
||||
return nil
|
||||
@@ -243,6 +217,7 @@ func LoadTarget(target string) (*TargetSpec, error) {
|
||||
}
|
||||
goarch := map[string]string{ // map from LLVM arch to Go arch
|
||||
"i386": "386",
|
||||
"i686": "386",
|
||||
"x86_64": "amd64",
|
||||
"aarch64": "arm64",
|
||||
"armv7": "arm",
|
||||
@@ -258,39 +233,40 @@ 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",
|
||||
FlashMethod: "native",
|
||||
Triple: triple,
|
||||
GOOS: goos,
|
||||
GOARCH: goarch,
|
||||
BuildTags: []string{goos, goarch},
|
||||
Compiler: "clang",
|
||||
Linker: "cc",
|
||||
CFlags: []string{"--target=" + triple},
|
||||
GDB: "gdb",
|
||||
PortReset: "false",
|
||||
}
|
||||
if goos == "darwin" {
|
||||
spec.LDFlags = append(spec.LDFlags, "-Wl,-dead_strip")
|
||||
} 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")
|
||||
}
|
||||
if goarch != runtime.GOARCH {
|
||||
// Some educated guesses as to how to invoke helper programs.
|
||||
spec.GDB = "gdb-multiarch"
|
||||
if goarch == "arm" && goos == "linux" {
|
||||
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/arm-linux-gnueabihf")
|
||||
spec.Linker = "arm-linux-gnueabihf-gcc"
|
||||
spec.GDB = "arm-linux-gnueabihf-gdb"
|
||||
spec.Emulator = []string{"qemu-arm", "-L", "/usr/arm-linux-gnueabihf"}
|
||||
}
|
||||
if goarch == "arm64" && goos == "linux" {
|
||||
spec.CFlags = append(spec.CFlags, "--sysroot=/usr/aarch64-linux-gnu")
|
||||
spec.Linker = "aarch64-linux-gnu-gcc"
|
||||
spec.GDB = "aarch64-linux-gnu-gdb"
|
||||
spec.Emulator = []string{"qemu-aarch64", "-L", "/usr/aarch64-linux-gnu"}
|
||||
}
|
||||
if goarch == "386" {
|
||||
spec.CFlags = []string{"-m32"}
|
||||
spec.LDFlags = []string{"-m32"}
|
||||
if goarch == "386" && runtime.GOARCH == "amd64" {
|
||||
spec.CFlags = append(spec.CFlags, "-m32")
|
||||
spec.LDFlags = append(spec.LDFlags, "-m32")
|
||||
}
|
||||
}
|
||||
return &spec, nil
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package compileopts
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadTarget(t *testing.T) {
|
||||
_, err := LoadTarget("arduino")
|
||||
@@ -17,3 +20,67 @@ func TestLoadTarget(t *testing.T) {
|
||||
t.Error("LoadTarget failed for wrong reason:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOverrideProperties(t *testing.T) {
|
||||
baseAutoStackSize := true
|
||||
base := &TargetSpec{
|
||||
GOOS: "baseGoos",
|
||||
CPU: "baseCpu",
|
||||
Features: []string{"bf1", "bf2"},
|
||||
BuildTags: []string{"bt1", "bt2"},
|
||||
Emulator: []string{"be1", "be2"},
|
||||
DefaultStackSize: 42,
|
||||
AutoStackSize: &baseAutoStackSize,
|
||||
}
|
||||
childAutoStackSize := false
|
||||
child := &TargetSpec{
|
||||
GOOS: "",
|
||||
CPU: "chlidCpu",
|
||||
Features: []string{"cf1", "cf2"},
|
||||
Emulator: []string{"ce1", "ce2"},
|
||||
AutoStackSize: &childAutoStackSize,
|
||||
DefaultStackSize: 64,
|
||||
}
|
||||
|
||||
base.overrideProperties(child)
|
||||
|
||||
if base.GOOS != "baseGoos" {
|
||||
t.Errorf("Overriding failed : got %v", base.GOOS)
|
||||
}
|
||||
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.BuildTags, []string{"bt1", "bt2"}) {
|
||||
t.Errorf("Overriding failed : got %v", base.BuildTags)
|
||||
}
|
||||
if !reflect.DeepEqual(base.Emulator, []string{"ce1", "ce2"}) {
|
||||
t.Errorf("Overriding failed : got %v", base.Emulator)
|
||||
}
|
||||
if *base.AutoStackSize != false {
|
||||
t.Errorf("Overriding failed : got %v", base.AutoStackSize)
|
||||
}
|
||||
if base.DefaultStackSize != 64 {
|
||||
t.Errorf("Overriding failed : got %v", base.DefaultStackSize)
|
||||
}
|
||||
|
||||
baseAutoStackSize = true
|
||||
base = &TargetSpec{
|
||||
AutoStackSize: &baseAutoStackSize,
|
||||
DefaultStackSize: 42,
|
||||
}
|
||||
child = &TargetSpec{
|
||||
AutoStackSize: nil,
|
||||
DefaultStackSize: 0,
|
||||
}
|
||||
base.overrideProperties(child)
|
||||
if *base.AutoStackSize != true {
|
||||
t.Errorf("Overriding failed : got %v", base.AutoStackSize)
|
||||
}
|
||||
if base.DefaultStackSize != 42 {
|
||||
t.Errorf("Overriding failed : got %v", base.DefaultStackSize)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+143
-80
@@ -4,16 +4,19 @@ package compiler
|
||||
// required by the Go programming language.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
"go/types"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitLookupBoundsCheck emits a bounds check before doing a lookup into a
|
||||
// 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 (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
|
||||
if frame.fn.IsNoBounds() {
|
||||
func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value, indexType types.Type) {
|
||||
if b.info.nobounds {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
return
|
||||
@@ -23,41 +26,29 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
|
||||
// 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 = c.builder.CreateZExt(index, arrayLen.Type(), "")
|
||||
index = b.CreateZExt(index, arrayLen.Type(), "")
|
||||
} else {
|
||||
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
|
||||
index = b.CreateSExt(index, arrayLen.Type(), "")
|
||||
}
|
||||
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
|
||||
// The index is bigger than the array length type, so extend it.
|
||||
arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
|
||||
arrayLen = b.CreateZExt(arrayLen, index.Type(), "")
|
||||
}
|
||||
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Now do the bounds check: index >= arrayLen
|
||||
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
|
||||
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("lookupPanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
|
||||
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic")
|
||||
}
|
||||
|
||||
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
|
||||
// createSliceBoundsCheck emits a bounds check before a slicing operation to make
|
||||
// sure it is within bounds.
|
||||
//
|
||||
// This function is both used for slicing a slice (low and high have their
|
||||
// normal meaning) and for creating a new slice, where 'capacity' means the
|
||||
// biggest possible slice capacity, 'low' means len and 'high' means cap. The
|
||||
// logic is the same in both cases.
|
||||
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
|
||||
if frame.fn.IsNoBounds() {
|
||||
func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lowType, highType, maxType *types.Basic) {
|
||||
if b.info.nobounds {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
return
|
||||
@@ -75,92 +66,164 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high, max l
|
||||
capacityType = max.Type()
|
||||
}
|
||||
if capacityType != capacity.Type() {
|
||||
capacity = c.builder.CreateZExt(capacity, capacityType, "")
|
||||
capacity = b.CreateZExt(capacity, capacityType, "")
|
||||
}
|
||||
|
||||
// Extend low and high to be the same size as capacity.
|
||||
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if lowType.Info()&types.IsUnsigned != 0 {
|
||||
low = c.builder.CreateZExt(low, capacityType, "")
|
||||
low = b.CreateZExt(low, capacityType, "")
|
||||
} else {
|
||||
low = c.builder.CreateSExt(low, capacityType, "")
|
||||
low = b.CreateSExt(low, capacityType, "")
|
||||
}
|
||||
}
|
||||
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if highType.Info()&types.IsUnsigned != 0 {
|
||||
high = c.builder.CreateZExt(high, capacityType, "")
|
||||
high = b.CreateZExt(high, capacityType, "")
|
||||
} else {
|
||||
high = c.builder.CreateSExt(high, capacityType, "")
|
||||
high = b.CreateSExt(high, capacityType, "")
|
||||
}
|
||||
}
|
||||
if max.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
||||
if maxType.Info()&types.IsUnsigned != 0 {
|
||||
max = c.builder.CreateZExt(max, capacityType, "")
|
||||
max = b.CreateZExt(max, capacityType, "")
|
||||
} else {
|
||||
max = c.builder.CreateSExt(max, capacityType, "")
|
||||
max = b.CreateSExt(max, capacityType, "")
|
||||
}
|
||||
}
|
||||
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Now do the bounds check: low > high || high > capacity
|
||||
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
|
||||
outOfBounds3 := c.builder.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
|
||||
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
|
||||
outOfBounds = c.builder.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
|
||||
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("slicePanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
outOfBounds1 := b.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
||||
outOfBounds2 := b.CreateICmp(llvm.IntUGT, high, max, "slice.highmax")
|
||||
outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
|
||||
outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
|
||||
outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
|
||||
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
|
||||
}
|
||||
|
||||
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
|
||||
// has no effect in well-behaved programs, but makes sure no uncaught nil
|
||||
// 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) {
|
||||
if b.info.nobounds {
|
||||
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||
// checking.
|
||||
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, "")
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate (^uintptr(0)) >> 1, which is the max value that fits in an
|
||||
// uintptr if uintptrs were signed.
|
||||
maxBufSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)), llvm.ConstInt(b.uintptrType, 1, false))
|
||||
if elementSize > maxBufSize.ZExtValue() {
|
||||
b.addError(pos, fmt.Sprintf("channel element type is too big (%v bytes)", elementSize))
|
||||
return
|
||||
}
|
||||
// Avoid divide-by-zero.
|
||||
if elementSize == 0 {
|
||||
elementSize = 1
|
||||
}
|
||||
// Make the maxBufSize actually the maximum allowed value (in number of
|
||||
// elements in the channel buffer).
|
||||
maxBufSize = llvm.ConstUDiv(maxBufSize, llvm.ConstInt(b.uintptrType, elementSize, false))
|
||||
|
||||
// Make sure maxBufSize has the same type as bufSize.
|
||||
if maxBufSize.Type() != bufSize.Type() {
|
||||
maxBufSize = llvm.ConstZExt(maxBufSize, bufSize.Type())
|
||||
}
|
||||
|
||||
// Do the check for a too large (or negative) buffer size.
|
||||
bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
|
||||
b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic")
|
||||
}
|
||||
|
||||
// createNilCheck checks whether the given pointer is nil, and panics if it is.
|
||||
// It has no effect in well-behaved programs, but makes sure no uncaught nil
|
||||
// pointer dereferences exist in valid Go code.
|
||||
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
|
||||
func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix string) {
|
||||
// Check whether we need to emit this check at all.
|
||||
if !ptr.IsAGlobalValue().IsNil() {
|
||||
return
|
||||
}
|
||||
|
||||
// Check whether this is a nil pointer.
|
||||
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
switch inst := inst.(type) {
|
||||
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
|
||||
// must be a valid (non-nil) pointer. No nil checking is necessary.
|
||||
return
|
||||
case *ssa.Convert:
|
||||
// This is a pointer that comes from a conversion from unsafe.Pointer.
|
||||
// Don't do nil checking because this is unsafe code and the code should
|
||||
// know what it is doing.
|
||||
// Note: all *ssa.Convert instructions that result in a pointer must
|
||||
// come from unsafe.Pointer. Testing here for unsafe.Pointer to be sure.
|
||||
if inst.X.Type() == types.Typ[types.UnsafePointer] {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Compare against nil.
|
||||
var isnil llvm.Value
|
||||
if ptr.Type().PointerAddressSpace() == 0 {
|
||||
// Do the nil check using the isnil builtin, which marks the parameter
|
||||
// as nocapture.
|
||||
// The reason it has to go through a builtin, is that a regular icmp
|
||||
// instruction may capture the pointer in LLVM semantics, see
|
||||
// https://reviews.llvm.org/D60047 for details. Pointer capturing
|
||||
// unfortunately breaks escape analysis, so we use this trick to let the
|
||||
// functionattr pass know that this pointer doesn't really escape.
|
||||
ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "")
|
||||
isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
|
||||
} else {
|
||||
// Do the nil check using a regular icmp. This can happen with function
|
||||
// pointers on AVR, which don't benefit from escape analysis anyway.
|
||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||
isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||
}
|
||||
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
|
||||
// We previously used a hack to make sure this wouldn't break escape
|
||||
// analysis, but this is not necessary anymore since
|
||||
// https://reviews.llvm.org/D60047 has been merged.
|
||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||
isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||
|
||||
// Fail: this is a nil pointer, exit with a panic.
|
||||
c.builder.SetInsertPointAtEnd(faultBlock)
|
||||
c.createRuntimeCall("nilPanic", nil, "")
|
||||
c.builder.CreateUnreachable()
|
||||
|
||||
// Ok: this is a valid pointer.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
// Emit the nil check in IR.
|
||||
b.createRuntimeAssert(isnil, blockPrefix, "nilPanic")
|
||||
}
|
||||
|
||||
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
|
||||
// This function assumes that the shift value is signed.
|
||||
func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
|
||||
if b.info.nobounds {
|
||||
// Function disabled bounds checking - skip shift check.
|
||||
return
|
||||
}
|
||||
|
||||
// isNegative = shift < 0
|
||||
isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "")
|
||||
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Check whether we can resolve this check at compile time.
|
||||
if !assert.IsAConstantInt().IsNil() {
|
||||
val := assert.ZExtValue()
|
||||
if val == 0 {
|
||||
// Everything is constant so the check does not have to be emitted
|
||||
// in IR. This avoids emitting some redundant IR.
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
faultBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".throw")
|
||||
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, blockPrefix+".next")
|
||||
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
|
||||
// Now branch to the out-of-bounds or the regular block.
|
||||
b.CreateCondBr(assert, faultBlock, nextBlock)
|
||||
|
||||
// Fail: the assert triggered so panic.
|
||||
b.SetInsertPointAtEnd(faultBlock)
|
||||
b.createRuntimeCall(assertFunc, nil, "")
|
||||
b.CreateUnreachable()
|
||||
|
||||
// Ok: assert didn't trigger so continue normally.
|
||||
b.SetInsertPointAtEnd(nextBlock)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// createAtomicOp lowers an atomic library call by lowering it as an LLVM atomic
|
||||
// operation. It returns the result of the operation and true if the call could
|
||||
// be lowered inline, and false otherwise.
|
||||
func (b *builder) createAtomicOp(call *ssa.CallCommon) (llvm.Value, bool) {
|
||||
name := call.Value.(*ssa.Function).Name()
|
||||
switch name {
|
||||
case "AddInt32", "AddInt64", "AddUint32", "AddUint64", "AddUintptr":
|
||||
ptr := b.getValue(call.Args[0])
|
||||
val := b.getValue(call.Args[1])
|
||||
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
|
||||
case "SwapInt32", "SwapInt64", "SwapUint32", "SwapUint64", "SwapUintptr", "SwapPointer":
|
||||
ptr := b.getValue(call.Args[0])
|
||||
val := b.getValue(call.Args[1])
|
||||
isPointer := val.Type().TypeKind() == llvm.PointerTypeKind
|
||||
if isPointer {
|
||||
// atomicrmw only supports integers, so cast to an integer.
|
||||
val = b.CreatePtrToInt(val, b.uintptrType, "")
|
||||
ptr = b.CreateBitCast(ptr, llvm.PointerType(val.Type(), 0), "")
|
||||
}
|
||||
oldVal := b.CreateAtomicRMW(llvm.AtomicRMWBinOpXchg, ptr, val, llvm.AtomicOrderingSequentiallyConsistent, true)
|
||||
if isPointer {
|
||||
oldVal = b.CreateIntToPtr(oldVal, b.i8ptrType, "")
|
||||
}
|
||||
return oldVal, true
|
||||
case "CompareAndSwapInt32", "CompareAndSwapInt64", "CompareAndSwapUint32", "CompareAndSwapUint64", "CompareAndSwapUintptr", "CompareAndSwapPointer":
|
||||
ptr := b.getValue(call.Args[0])
|
||||
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") {
|
||||
// Work around a bug in LLVM, at least LLVM 11:
|
||||
// https://reviews.llvm.org/D95891
|
||||
// Check for armv6m, armv7, armv7em, 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() {
|
||||
// Declare the function if it isn't already declared.
|
||||
i64Type := b.ctx.Int64Type()
|
||||
fnType := llvm.FunctionType(i64Type, []llvm.Type{llvm.PointerType(i64Type, 0), i64Type, i64Type}, false)
|
||||
compareAndSwap = llvm.AddFunction(b.mod, "__sync_val_compare_and_swap_8", fnType)
|
||||
}
|
||||
actualOldValue := b.CreateCall(compareAndSwap, []llvm.Value{ptr, old, newVal}, "")
|
||||
// The __sync_val_compare_and_swap_8 function returns the old
|
||||
// value. However, we shouldn't return the old value, we should
|
||||
// return whether the compare/exchange was successful. This is
|
||||
// easily done by comparing the returned (actual) old value with
|
||||
// the expected old value passed to
|
||||
// __sync_val_compare_and_swap_8.
|
||||
swapped := b.CreateICmp(llvm.IntEQ, old, actualOldValue, "")
|
||||
return swapped, true
|
||||
}
|
||||
}
|
||||
tuple := b.CreateAtomicCmpXchg(ptr, old, newVal, llvm.AtomicOrderingSequentiallyConsistent, llvm.AtomicOrderingSequentiallyConsistent, true)
|
||||
swapped := b.CreateExtractValue(tuple, 1, "")
|
||||
return swapped, true
|
||||
case "LoadInt32", "LoadInt64", "LoadUint32", "LoadUint64", "LoadUintptr", "LoadPointer":
|
||||
ptr := b.getValue(call.Args[0])
|
||||
val := b.CreateLoad(ptr, "")
|
||||
val.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
|
||||
val.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
|
||||
return val, true
|
||||
case "StoreInt32", "StoreInt64", "StoreUint32", "StoreUint64", "StoreUintptr", "StorePointer":
|
||||
ptr := b.getValue(call.Args[0])
|
||||
val := b.getValue(call.Args[1])
|
||||
store := b.CreateStore(val, ptr)
|
||||
store.SetOrdering(llvm.AtomicOrderingSequentiallyConsistent)
|
||||
store.SetAlignment(b.targetData.PrefTypeAlignment(val.Type())) // required
|
||||
return store, true
|
||||
default:
|
||||
return llvm.Value{}, false
|
||||
}
|
||||
}
|
||||
+167
-66
@@ -1,7 +1,9 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/types"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -11,62 +13,80 @@ import (
|
||||
|
||||
// The maximum number of arguments that can be expanded from a single struct. If
|
||||
// a struct contains more fields, it is passed as a struct without expanding.
|
||||
const MaxFieldsPerParam = 3
|
||||
const maxFieldsPerParam = 3
|
||||
|
||||
// Shortcut: create a call to runtime.<fnName> with the given arguments.
|
||||
func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
|
||||
runtimePkg := c.ir.Program.ImportedPackage("runtime")
|
||||
member := runtimePkg.Members[fnName]
|
||||
if member == nil {
|
||||
panic("trying to call runtime." + fnName)
|
||||
}
|
||||
fn := c.ir.GetFunction(member.(*ssa.Function))
|
||||
if fn.LLVMFn.IsNil() {
|
||||
panic(fmt.Errorf("function %s does not appear in LLVM IR", fnName))
|
||||
}
|
||||
if !fn.IsExported() {
|
||||
args = append(args, llvm.Undef(c.i8ptrType)) // unused context parameter
|
||||
args = append(args, llvm.ConstPointerNull(c.i8ptrType)) // coroutine handle
|
||||
}
|
||||
return c.createCall(fn.LLVMFn, args, name)
|
||||
// paramInfo contains some information collected about a function parameter,
|
||||
// useful while declaring or defining a function.
|
||||
type paramInfo struct {
|
||||
llvmType llvm.Type
|
||||
name string // name, possibly with suffixes for e.g. struct fields
|
||||
flags paramFlags
|
||||
}
|
||||
|
||||
// Create a call to the given function with the arguments possibly expanded.
|
||||
func (c *Compiler) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
|
||||
// paramFlags identifies parameter attributes for flags. Most importantly, it
|
||||
// determines which parameters are dereferenceable_or_null and which aren't.
|
||||
type paramFlags uint8
|
||||
|
||||
const (
|
||||
// Parameter may have the deferenceable_or_null attribute. This attribute
|
||||
// cannot be applied to unsafe.Pointer and to the data pointer of slices.
|
||||
paramIsDeferenceableOrNull = 1 << iota
|
||||
)
|
||||
|
||||
// createCall creates a new call to runtime.<fnName> with the given arguments.
|
||||
func (b *builder) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value {
|
||||
fn := b.program.ImportedPackage("runtime").Members[fnName].(*ssa.Function)
|
||||
llvmFn := b.getFunction(fn)
|
||||
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
|
||||
return b.createCall(llvmFn, args, name)
|
||||
}
|
||||
|
||||
// createCall creates a call to the given function with the arguments possibly
|
||||
// expanded.
|
||||
func (b *builder) createCall(fn llvm.Value, args []llvm.Value, name string) llvm.Value {
|
||||
expanded := make([]llvm.Value, 0, len(args))
|
||||
for _, arg := range args {
|
||||
fragments := c.expandFormalParam(arg)
|
||||
fragments := b.expandFormalParam(arg)
|
||||
expanded = append(expanded, fragments...)
|
||||
}
|
||||
return c.builder.CreateCall(fn, expanded, name)
|
||||
return b.CreateCall(fn, expanded, name)
|
||||
}
|
||||
|
||||
// Expand an argument type to a list that can be used in a function call
|
||||
// parameter list.
|
||||
func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type {
|
||||
func expandFormalParamType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := c.flattenAggregateType(t)
|
||||
if len(fields) <= MaxFieldsPerParam {
|
||||
return fields
|
||||
fieldInfos := flattenAggregateType(t, name, goType)
|
||||
if len(fieldInfos) <= maxFieldsPerParam {
|
||||
return fieldInfos
|
||||
} else {
|
||||
// failed to lower
|
||||
return []llvm.Type{t}
|
||||
}
|
||||
default:
|
||||
// TODO: split small arrays
|
||||
return []llvm.Type{t}
|
||||
}
|
||||
// TODO: split small arrays
|
||||
return []paramInfo{
|
||||
{
|
||||
llvmType: t,
|
||||
name: name,
|
||||
flags: getTypeFlags(goType),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Expand an argument type to a list of offsets from the start of the object.
|
||||
// Used together with expandFormalParam to get the offset of each value from the
|
||||
// start of the non-expanded value.
|
||||
func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
||||
// expandFormalParamOffsets returns a list of offsets from the start of an
|
||||
// object of type t after it would have been split up by expandFormalParam. This
|
||||
// is useful for debug information, where it is necessary to know the offset
|
||||
// from the start of the combined object.
|
||||
func (b *builder) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := c.flattenAggregateTypeOffsets(t)
|
||||
if len(fields) <= MaxFieldsPerParam {
|
||||
fields := b.flattenAggregateTypeOffsets(t)
|
||||
if len(fields) <= maxFieldsPerParam {
|
||||
return fields
|
||||
} else {
|
||||
// failed to lower
|
||||
@@ -78,14 +98,17 @@ func (c *Compiler) expandFormalParamOffsets(t llvm.Type) []uint64 {
|
||||
}
|
||||
}
|
||||
|
||||
// Equivalent of expandFormalParamType for parameter values.
|
||||
func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
// expandFormalParam splits a formal param value into pieces, so it can be
|
||||
// passed directly as part of a function call. For example, it splits up small
|
||||
// structs into individual fields. It is the equivalent of expandFormalParamType
|
||||
// for parameter values.
|
||||
func (b *builder) expandFormalParam(v llvm.Value) []llvm.Value {
|
||||
switch v.Type().TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fieldTypes := c.flattenAggregateType(v.Type())
|
||||
if len(fieldTypes) <= MaxFieldsPerParam {
|
||||
fields := c.flattenAggregate(v)
|
||||
if len(fields) != len(fieldTypes) {
|
||||
fieldInfos := flattenAggregateType(v.Type(), "", nil)
|
||||
if len(fieldInfos) <= maxFieldsPerParam {
|
||||
fields := b.flattenAggregate(v)
|
||||
if len(fields) != len(fieldInfos) {
|
||||
panic("type and value param lowering don't match")
|
||||
}
|
||||
return fields
|
||||
@@ -101,24 +124,98 @@ func (c *Compiler) 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 (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type {
|
||||
func flattenAggregateType(t llvm.Type, name string, goType types.Type) []paramInfo {
|
||||
typeFlags := getTypeFlags(goType)
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := make([]llvm.Type, 0, t.StructElementTypesCount())
|
||||
for _, subfield := range t.StructElementTypes() {
|
||||
subfields := c.flattenAggregateType(subfield)
|
||||
fields = append(fields, subfields...)
|
||||
paramInfos := make([]paramInfo, 0, t.StructElementTypesCount())
|
||||
for i, subfield := range t.StructElementTypes() {
|
||||
suffix := strconv.Itoa(i)
|
||||
if goType != nil {
|
||||
// Try to come up with a good suffix for this struct field,
|
||||
// depending on which Go type it's based on.
|
||||
switch goType := goType.Underlying().(type) {
|
||||
case *types.Interface:
|
||||
suffix = []string{"typecode", "value"}[i]
|
||||
case *types.Slice:
|
||||
suffix = []string{"data", "len", "cap"}[i]
|
||||
case *types.Struct:
|
||||
suffix = goType.Field(i).Name()
|
||||
case *types.Basic:
|
||||
switch goType.Kind() {
|
||||
case types.Complex64, types.Complex128:
|
||||
suffix = []string{"r", "i"}[i]
|
||||
case types.String:
|
||||
suffix = []string{"data", "len"}[i]
|
||||
}
|
||||
case *types.Signature:
|
||||
suffix = []string{"context", "funcptr"}[i]
|
||||
}
|
||||
}
|
||||
subInfos := flattenAggregateType(subfield, name+"."+suffix, extractSubfield(goType, i))
|
||||
for i := range subInfos {
|
||||
subInfos[i].flags |= typeFlags
|
||||
}
|
||||
paramInfos = append(paramInfos, subInfos...)
|
||||
}
|
||||
return fields
|
||||
return paramInfos
|
||||
default:
|
||||
return []llvm.Type{t}
|
||||
return []paramInfo{
|
||||
{
|
||||
llvmType: t,
|
||||
name: name,
|
||||
flags: typeFlags,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the offsets from the start of the object if this object type were
|
||||
// flattened like in flattenAggregate. Used together with flattenAggregate to
|
||||
// know the start indices of each value in the non-flattened object.
|
||||
func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
// getTypeFlags returns the type flags for a given type. It will not recurse
|
||||
// into sub-types (such as in structs).
|
||||
func getTypeFlags(t types.Type) paramFlags {
|
||||
if t == nil {
|
||||
return 0
|
||||
}
|
||||
switch t.Underlying().(type) {
|
||||
case *types.Pointer:
|
||||
// Pointers in Go must either point to an object or be nil.
|
||||
return paramIsDeferenceableOrNull
|
||||
case *types.Chan, *types.Map:
|
||||
// Channels and maps are implemented as pointers pointing to some
|
||||
// object, and follow the same rules as *types.Pointer.
|
||||
return paramIsDeferenceableOrNull
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// extractSubfield extracts a field from a struct, or returns null if this is
|
||||
// not a struct and thus no subfield can be obtained.
|
||||
func extractSubfield(t types.Type, field int) types.Type {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
switch t := t.Underlying().(type) {
|
||||
case *types.Struct:
|
||||
return t.Field(field).Type()
|
||||
case *types.Interface, *types.Slice, *types.Basic, *types.Signature:
|
||||
// These Go types are (sometimes) implemented as LLVM structs but can't
|
||||
// really be split further up in Go (with the possible exception of
|
||||
// complex numbers).
|
||||
return nil
|
||||
default:
|
||||
// This should be unreachable.
|
||||
panic("cannot split subfield: " + t.String())
|
||||
}
|
||||
}
|
||||
|
||||
// flattenAggregateTypeOffset returns the offsets from the start of an object of
|
||||
// type t if this object were flattened like in flattenAggregate. Used together
|
||||
// with flattenAggregate to know the start indices of each value in the
|
||||
// non-flattened object.
|
||||
//
|
||||
// Note: this is an implementation detail, use expandFormalParamOffsets instead.
|
||||
func (c *compilerContext) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
fields := make([]uint64, 0, t.StructElementTypesCount())
|
||||
@@ -136,15 +233,15 @@ func (c *Compiler) flattenAggregateTypeOffsets(t llvm.Type) []uint64 {
|
||||
}
|
||||
}
|
||||
|
||||
// Break down a struct into its elementary types for argument passing. The value
|
||||
// equivalent of flattenAggregateType
|
||||
func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
|
||||
// flattenAggregate breaks down a struct into its elementary values for argument
|
||||
// passing. It is the value equivalent of flattenAggregateType
|
||||
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() {
|
||||
subfield := c.builder.CreateExtractValue(v, i, "")
|
||||
subfields := c.flattenAggregate(subfield)
|
||||
subfield := b.CreateExtractValue(v, i, "")
|
||||
subfields := b.flattenAggregate(subfield)
|
||||
fields = append(fields, subfields...)
|
||||
}
|
||||
return fields
|
||||
@@ -153,25 +250,29 @@ func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value {
|
||||
}
|
||||
}
|
||||
|
||||
// Collapse a list of fields into its original value.
|
||||
func (c *Compiler) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
|
||||
param, remaining := c.collapseFormalParamInternal(t, fields)
|
||||
// collapseFormalParam combines an aggregate object back into the original
|
||||
// value. This is used to join multiple LLVM parameters into a single Go value
|
||||
// in the function entry block.
|
||||
func (b *builder) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value {
|
||||
param, remaining := b.collapseFormalParamInternal(t, fields)
|
||||
if len(remaining) != 0 {
|
||||
panic("failed to expand back all fields")
|
||||
}
|
||||
return param
|
||||
}
|
||||
|
||||
// Returns (value, remainingFields). Used by collapseFormalParam.
|
||||
func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
|
||||
// collapseFormalParamInternal is an implementation detail of
|
||||
// collapseFormalParam: it works by recursing until there are no fields left.
|
||||
func (b *builder) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) {
|
||||
switch t.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam {
|
||||
flattened := flattenAggregateType(t, "", nil)
|
||||
if len(flattened) <= maxFieldsPerParam {
|
||||
value := llvm.ConstNull(t)
|
||||
for i, subtyp := range t.StructElementTypes() {
|
||||
structField, remaining := c.collapseFormalParamInternal(subtyp, fields)
|
||||
structField, remaining := b.collapseFormalParamInternal(subtyp, fields)
|
||||
fields = remaining
|
||||
value = c.builder.CreateInsertValue(value, structField, i, "")
|
||||
value = b.CreateInsertValue(value, structField, i, "")
|
||||
}
|
||||
return value, fields
|
||||
} else {
|
||||
|
||||
+101
-86
@@ -11,73 +11,88 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) emitMakeChan(frame *Frame, expr *ssa.MakeChan) llvm.Value {
|
||||
elementSize := c.targetData.TypeAllocSize(c.getLLVMType(expr.Type().(*types.Chan).Elem()))
|
||||
elementSizeValue := llvm.ConstInt(c.uintptrType, elementSize, false)
|
||||
bufSize := c.getValue(frame, expr.Size)
|
||||
return c.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
|
||||
func (b *builder) createMakeChan(expr *ssa.MakeChan) llvm.Value {
|
||||
elementSize := b.targetData.TypeAllocSize(b.getLLVMType(expr.Type().Underlying().(*types.Chan).Elem()))
|
||||
elementSizeValue := llvm.ConstInt(b.uintptrType, elementSize, false)
|
||||
bufSize := b.getValue(expr.Size)
|
||||
b.createChanBoundsCheck(elementSize, bufSize, expr.Size.Type().Underlying().(*types.Basic), expr.Pos())
|
||||
if bufSize.Type().IntTypeWidth() < b.uintptrType.IntTypeWidth() {
|
||||
bufSize = b.CreateZExt(bufSize, b.uintptrType, "")
|
||||
} else if bufSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
|
||||
bufSize = b.CreateTrunc(bufSize, b.uintptrType, "")
|
||||
}
|
||||
return b.createRuntimeCall("chanMake", []llvm.Value{elementSizeValue, bufSize}, "")
|
||||
}
|
||||
|
||||
// emitChanSend emits a pseudo chan send operation. It is lowered to the actual
|
||||
// channel send operation during goroutine lowering.
|
||||
func (c *Compiler) emitChanSend(frame *Frame, instr *ssa.Send) {
|
||||
ch := c.getValue(frame, instr.Chan)
|
||||
chanValue := c.getValue(frame, instr.X)
|
||||
// createChanSend emits a pseudo chan send operation. It is lowered to the
|
||||
// actual channel send operation during goroutine lowering.
|
||||
func (b *builder) createChanSend(instr *ssa.Send) {
|
||||
ch := b.getValue(instr.Chan)
|
||||
chanValue := b.getValue(instr.X)
|
||||
|
||||
// store value-to-send
|
||||
valueType := c.getLLVMType(instr.X.Type())
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
||||
c.builder.CreateStore(chanValue, valueAlloca)
|
||||
valueType := b.getLLVMType(instr.X.Type())
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
||||
b.CreateStore(chanValue, valueAlloca)
|
||||
|
||||
// Allocate blockedlist buffer.
|
||||
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
|
||||
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
|
||||
|
||||
// Do the send.
|
||||
c.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast}, "")
|
||||
b.createRuntimeCall("chanSend", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
|
||||
|
||||
// End the lifetime of the alloca.
|
||||
// End the lifetime of the allocas.
|
||||
// This also works around a bug in CoroSplit, at least in LLVM 8:
|
||||
// https://bugs.llvm.org/show_bug.cgi?id=41742
|
||||
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
}
|
||||
|
||||
// emitChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||
// createChanRecv emits a pseudo chan receive operation. It is lowered to the
|
||||
// actual channel receive operation during goroutine lowering.
|
||||
func (c *Compiler) emitChanRecv(frame *Frame, unop *ssa.UnOp) llvm.Value {
|
||||
valueType := c.getLLVMType(unop.X.Type().(*types.Chan).Elem())
|
||||
ch := c.getValue(frame, unop.X)
|
||||
func (b *builder) createChanRecv(unop *ssa.UnOp) llvm.Value {
|
||||
valueType := b.getLLVMType(unop.X.Type().Underlying().(*types.Chan).Elem())
|
||||
ch := b.getValue(unop.X)
|
||||
|
||||
// Allocate memory to receive into.
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := c.createTemporaryAlloca(valueType, "chan.value")
|
||||
valueAlloca, valueAllocaCast, valueAllocaSize := b.createTemporaryAlloca(valueType, "chan.value")
|
||||
|
||||
// Allocate blockedlist buffer.
|
||||
channelBlockedList := b.mod.GetTypeByName("runtime.channelBlockedList")
|
||||
channelBlockedListAlloca, channelBlockedListAllocaCast, channelBlockedListAllocaSize := b.createTemporaryAlloca(channelBlockedList, "chan.blockedList")
|
||||
|
||||
// Do the receive.
|
||||
commaOk := c.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast}, "")
|
||||
received := c.builder.CreateLoad(valueAlloca, "chan.received")
|
||||
c.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
commaOk := b.createRuntimeCall("chanRecv", []llvm.Value{ch, valueAllocaCast, channelBlockedListAlloca}, "")
|
||||
received := b.CreateLoad(valueAlloca, "chan.received")
|
||||
b.emitLifetimeEnd(channelBlockedListAllocaCast, channelBlockedListAllocaSize)
|
||||
b.emitLifetimeEnd(valueAllocaCast, valueAllocaSize)
|
||||
|
||||
if unop.CommaOk {
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{valueType, c.ctx.Int1Type()}, false))
|
||||
tuple = c.builder.CreateInsertValue(tuple, received, 0, "")
|
||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "")
|
||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{valueType, b.ctx.Int1Type()}, false))
|
||||
tuple = b.CreateInsertValue(tuple, received, 0, "")
|
||||
tuple = b.CreateInsertValue(tuple, commaOk, 1, "")
|
||||
return tuple
|
||||
} else {
|
||||
return received
|
||||
}
|
||||
}
|
||||
|
||||
// emitChanClose closes the given channel.
|
||||
func (c *Compiler) emitChanClose(frame *Frame, param ssa.Value) {
|
||||
ch := c.getValue(frame, param)
|
||||
c.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
||||
// createChanClose closes the given channel.
|
||||
func (b *builder) createChanClose(ch llvm.Value) {
|
||||
b.createRuntimeCall("chanClose", []llvm.Value{ch}, "")
|
||||
}
|
||||
|
||||
// emitSelect emits all IR necessary for a select statements. That's a
|
||||
// createSelect emits all IR necessary for a select statements. That's a
|
||||
// non-trivial amount of code because select is very complex to implement.
|
||||
func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||
func (b *builder) createSelect(expr *ssa.Select) llvm.Value {
|
||||
if len(expr.States) == 0 {
|
||||
// Shortcuts for some simple selects.
|
||||
llvmType := c.getLLVMType(expr.Type())
|
||||
llvmType := b.getLLVMType(expr.Type())
|
||||
if expr.Blocking {
|
||||
// Blocks forever:
|
||||
// select {}
|
||||
c.createRuntimeCall("deadlock", nil, "")
|
||||
b.createRuntimeCall("deadlock", nil, "")
|
||||
return llvm.Undef(llvmType)
|
||||
} else {
|
||||
// No-op:
|
||||
@@ -85,7 +100,7 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||
// default:
|
||||
// }
|
||||
retval := llvm.Undef(llvmType)
|
||||
retval = c.builder.CreateInsertValue(retval, llvm.ConstInt(c.intType, 0xffffffffffffffff, true), 0, "")
|
||||
retval = b.CreateInsertValue(retval, llvm.ConstInt(b.intType, 0xffffffffffffffff, true), 0, "")
|
||||
return retval // {-1, false}
|
||||
}
|
||||
}
|
||||
@@ -103,30 +118,30 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||
recvbufAlign := 0
|
||||
hasReceives := false
|
||||
var selectStates []llvm.Value
|
||||
chanSelectStateType := c.getLLVMRuntimeType("chanSelectState")
|
||||
chanSelectStateType := b.getLLVMRuntimeType("chanSelectState")
|
||||
for _, state := range expr.States {
|
||||
ch := c.getValue(frame, state.Chan)
|
||||
ch := b.getValue(state.Chan)
|
||||
selectState := llvm.ConstNull(chanSelectStateType)
|
||||
selectState = c.builder.CreateInsertValue(selectState, ch, 0, "")
|
||||
selectState = b.CreateInsertValue(selectState, ch, 0, "")
|
||||
switch state.Dir {
|
||||
case types.RecvOnly:
|
||||
// Make sure the receive buffer is big enough and has the correct alignment.
|
||||
llvmType := c.getLLVMType(state.Chan.Type().(*types.Chan).Elem())
|
||||
if size := c.targetData.TypeAllocSize(llvmType); size > recvbufSize {
|
||||
llvmType := b.getLLVMType(state.Chan.Type().Underlying().(*types.Chan).Elem())
|
||||
if size := b.targetData.TypeAllocSize(llvmType); size > recvbufSize {
|
||||
recvbufSize = size
|
||||
}
|
||||
if align := c.targetData.ABITypeAlignment(llvmType); align > recvbufAlign {
|
||||
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.
|
||||
sendValue := c.getValue(frame, state.Send)
|
||||
alloca := llvmutil.CreateEntryBlockAlloca(c.builder, sendValue.Type(), "select.send.value")
|
||||
c.builder.CreateStore(sendValue, alloca)
|
||||
ptr := c.builder.CreateBitCast(alloca, c.i8ptrType, "")
|
||||
selectState = c.builder.CreateInsertValue(selectState, ptr, 1, "")
|
||||
sendValue := b.getValue(state.Send)
|
||||
alloca := llvmutil.CreateEntryBlockAlloca(b.Builder, sendValue.Type(), "select.send.value")
|
||||
b.CreateStore(sendValue, alloca)
|
||||
ptr := b.CreateBitCast(alloca, b.i8ptrType, "")
|
||||
selectState = b.CreateInsertValue(selectState, ptr, 1, "")
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
@@ -134,74 +149,74 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||
}
|
||||
|
||||
// Create a receive buffer, where the received value will be stored.
|
||||
recvbuf := llvm.Undef(c.i8ptrType)
|
||||
recvbuf := llvm.Undef(b.i8ptrType)
|
||||
if hasReceives {
|
||||
allocaType := llvm.ArrayType(c.ctx.Int8Type(), int(recvbufSize))
|
||||
recvbufAlloca, _, _ := c.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
|
||||
allocaType := llvm.ArrayType(b.ctx.Int8Type(), int(recvbufSize))
|
||||
recvbufAlloca, _, _ := b.createTemporaryAlloca(allocaType, "select.recvbuf.alloca")
|
||||
recvbufAlloca.SetAlignment(recvbufAlign)
|
||||
recvbuf = c.builder.CreateGEP(recvbufAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
recvbuf = b.CreateGEP(recvbufAlloca, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
}, "select.recvbuf")
|
||||
}
|
||||
|
||||
// Create the states slice (allocated on the stack).
|
||||
statesAllocaType := llvm.ArrayType(chanSelectStateType, len(selectStates))
|
||||
statesAlloca, statesI8, statesSize := c.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
|
||||
statesAlloca, statesI8, statesSize := b.createTemporaryAlloca(statesAllocaType, "select.states.alloca")
|
||||
for i, state := range selectStates {
|
||||
// Set each slice element to the appropriate channel.
|
||||
gep := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false),
|
||||
gep := b.CreateGEP(statesAlloca, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false),
|
||||
}, "")
|
||||
c.builder.CreateStore(state, gep)
|
||||
b.CreateStore(state, gep)
|
||||
}
|
||||
statesPtr := c.builder.CreateGEP(statesAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
statesPtr := b.CreateGEP(statesAlloca, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
}, "select.states")
|
||||
statesLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
||||
statesLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
|
||||
|
||||
// Do the select in the runtime.
|
||||
var results llvm.Value
|
||||
if expr.Blocking {
|
||||
// Stack-allocate operation structures.
|
||||
// If these were simply created as a slice, they would heap-allocate.
|
||||
chBlockAllocaType := llvm.ArrayType(c.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
|
||||
chBlockAlloca, chBlockAllocaPtr, chBlockSize := c.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
|
||||
chBlockLen := llvm.ConstInt(c.uintptrType, uint64(len(selectStates)), false)
|
||||
chBlockPtr := c.builder.CreateGEP(chBlockAlloca, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
chBlockAllocaType := llvm.ArrayType(b.getLLVMRuntimeType("channelBlockedList"), len(selectStates))
|
||||
chBlockAlloca, chBlockAllocaPtr, chBlockSize := b.createTemporaryAlloca(chBlockAllocaType, "select.block.alloca")
|
||||
chBlockLen := llvm.ConstInt(b.uintptrType, uint64(len(selectStates)), false)
|
||||
chBlockPtr := b.CreateGEP(chBlockAlloca, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
}, "select.block")
|
||||
|
||||
results = c.createRuntimeCall("chanSelect", []llvm.Value{
|
||||
results = b.createRuntimeCall("chanSelect", []llvm.Value{
|
||||
recvbuf,
|
||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||
chBlockPtr, chBlockLen, chBlockLen, // []channelBlockList
|
||||
}, "select.result")
|
||||
|
||||
// Terminate the lifetime of the operation structures.
|
||||
c.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
|
||||
b.emitLifetimeEnd(chBlockAllocaPtr, chBlockSize)
|
||||
} else {
|
||||
results = c.createRuntimeCall("tryChanSelect", []llvm.Value{
|
||||
results = b.createRuntimeCall("tryChanSelect", []llvm.Value{
|
||||
recvbuf,
|
||||
statesPtr, statesLen, statesLen, // []chanSelectState
|
||||
}, "select.result")
|
||||
}
|
||||
|
||||
// Terminate the lifetime of the states alloca.
|
||||
c.emitLifetimeEnd(statesI8, statesSize)
|
||||
b.emitLifetimeEnd(statesI8, statesSize)
|
||||
|
||||
// The result value does not include all the possible received values,
|
||||
// because we can't load them in advance. Instead, the *ssa.Extract
|
||||
// instruction will treat a *ssa.Select specially and load it there inline.
|
||||
// Store the receive alloca in a sidetable until we hit this extract
|
||||
// instruction.
|
||||
if frame.selectRecvBuf == nil {
|
||||
frame.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
|
||||
if b.selectRecvBuf == nil {
|
||||
b.selectRecvBuf = make(map[*ssa.Select]llvm.Value)
|
||||
}
|
||||
frame.selectRecvBuf[expr] = recvbuf
|
||||
b.selectRecvBuf[expr] = recvbuf
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -210,28 +225,28 @@ func (c *Compiler) emitSelect(frame *Frame, expr *ssa.Select) llvm.Value {
|
||||
// when extracting a value from a select statement (*ssa.Select). Because
|
||||
// *ssa.Select cannot load all values in advance, it does this later in the
|
||||
// *ssa.Extract expression.
|
||||
func (c *Compiler) getChanSelectResult(frame *Frame, expr *ssa.Extract) llvm.Value {
|
||||
func (b *builder) getChanSelectResult(expr *ssa.Extract) llvm.Value {
|
||||
if expr.Index == 0 {
|
||||
// index
|
||||
value := c.getValue(frame, expr.Tuple)
|
||||
index := c.builder.CreateExtractValue(value, expr.Index, "")
|
||||
if index.Type().IntTypeWidth() < c.intType.IntTypeWidth() {
|
||||
index = c.builder.CreateSExt(index, c.intType, "")
|
||||
value := b.getValue(expr.Tuple)
|
||||
index := b.CreateExtractValue(value, expr.Index, "")
|
||||
if index.Type().IntTypeWidth() < b.intType.IntTypeWidth() {
|
||||
index = b.CreateSExt(index, b.intType, "")
|
||||
}
|
||||
return index
|
||||
} else if expr.Index == 1 {
|
||||
// comma-ok
|
||||
value := c.getValue(frame, expr.Tuple)
|
||||
return c.builder.CreateExtractValue(value, expr.Index, "")
|
||||
value := b.getValue(expr.Tuple)
|
||||
return b.CreateExtractValue(value, expr.Index, "")
|
||||
} else {
|
||||
// Select statements are (index, ok, ...) where ... is a number of
|
||||
// received values, depending on how many receive statements there
|
||||
// are. They are all combined into one alloca (because only one
|
||||
// receive can proceed at a time) so we'll get that alloca, bitcast
|
||||
// it to the correct type, and dereference it.
|
||||
recvbuf := frame.selectRecvBuf[expr.Tuple.(*ssa.Select)]
|
||||
typ := llvm.PointerType(c.getLLVMType(expr.Type()), 0)
|
||||
ptr := c.builder.CreateBitCast(recvbuf, typ, "")
|
||||
return c.builder.CreateLoad(ptr, "")
|
||||
recvbuf := b.selectRecvBuf[expr.Tuple.(*ssa.Select)]
|
||||
typ := llvm.PointerType(b.getLLVMType(expr.Type()), 0)
|
||||
ptr := b.CreateBitCast(recvbuf, typ, "")
|
||||
return b.CreateLoad(ptr, "")
|
||||
}
|
||||
}
|
||||
|
||||
+1172
-1125
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,172 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"go/types"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"github.com/tinygo-org/tinygo/loader"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// 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")
|
||||
|
||||
// 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")
|
||||
if err != nil {
|
||||
t.Fatal("failed to load target:", err)
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
tests := []string{
|
||||
"basic.go",
|
||||
"pointer.go",
|
||||
"slice.go",
|
||||
"float.go",
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
t.Run(testCase, func(t *testing.T) {
|
||||
// Load entire program AST into memory.
|
||||
lprogram, err := loader.Load(config, []string{"./testdata/" + testCase}, config.ClangHeaders, types.Config{
|
||||
Sizes: Sizes(machine),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal("failed to create target machine:", err)
|
||||
}
|
||||
err = lprogram.Parse()
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse test case %s: %s", testCase, err)
|
||||
}
|
||||
|
||||
// Compile AST to IR.
|
||||
pkg := lprogram.MainPkg()
|
||||
mod, errs := CompilePackage(testCase, pkg, machine, compilerConfig, false)
|
||||
if errs != nil {
|
||||
for _, err := range errs {
|
||||
t.Log("error:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Optimize IR a little.
|
||||
funcPasses := llvm.NewFunctionPassManagerForModule(mod)
|
||||
defer funcPasses.Dispose()
|
||||
funcPasses.AddInstructionCombiningPass()
|
||||
funcPasses.InitializeFunc()
|
||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
funcPasses.RunFunc(fn)
|
||||
}
|
||||
funcPasses.FinalizeFunc()
|
||||
|
||||
outfile := "./testdata/" + testCase[:len(testCase)-3] + ".ll"
|
||||
|
||||
// Update test if needed. Do not check the result.
|
||||
if *flagUpdate {
|
||||
err := ioutil.WriteFile(outfile, []byte(mod.String()), 0666)
|
||||
if err != nil {
|
||||
t.Error("failed to write updated output file:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
expected, err := ioutil.ReadFile(outfile)
|
||||
if err != nil {
|
||||
t.Fatal("failed to read golden file:", err)
|
||||
}
|
||||
|
||||
if !fuzzyEqualIR(mod.String(), string(expected)) {
|
||||
t.Errorf("output does not match expected output:\n%s", mod.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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.).
|
||||
func fuzzyEqualIR(s1, s2 string) bool {
|
||||
lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
|
||||
lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
|
||||
if len(lines1) != len(lines2) {
|
||||
return false
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// filterIrrelevantIRLines removes lines from the input slice of strings that
|
||||
// are not relevant in comparing IR. For example, empty lines and comments are
|
||||
// stripped out.
|
||||
func filterIrrelevantIRLines(lines []string) []string {
|
||||
var out []string
|
||||
llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0])
|
||||
if err != nil {
|
||||
// Note: this should never happen and if it does, it will always happen
|
||||
// for a particular build because llvm.Version is a constant.
|
||||
panic(err)
|
||||
}
|
||||
for _, line := range lines {
|
||||
line = strings.Split(line, ";")[0] // strip out comments/info
|
||||
line = strings.TrimRight(line, "\r ") // drop '\r' on Windows and remove trailing spaces from comments
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "source_filename = ") {
|
||||
continue
|
||||
}
|
||||
if llvmVersion < 10 && strings.HasPrefix(line, "attributes ") {
|
||||
// Ignore attribute groups. These may change between LLVM versions.
|
||||
// Right now test outputs are for LLVM 10.
|
||||
continue
|
||||
}
|
||||
if llvmVersion < 10 && strings.HasPrefix(line, "target datalayout ") {
|
||||
// Ignore the target layout. This may change between LLVM versions.
|
||||
continue
|
||||
}
|
||||
out = append(out, line)
|
||||
}
|
||||
return out
|
||||
}
|
||||
+227
-124
@@ -14,8 +14,9 @@ package compiler
|
||||
// frames.
|
||||
|
||||
import (
|
||||
"go/types"
|
||||
|
||||
"github.com/tinygo-org/tinygo/compiler/llvmutil"
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
@@ -23,16 +24,18 @@ import (
|
||||
// deferInitFunc sets up this function for future deferred calls. It must be
|
||||
// called from within the entry block when this function contains deferred
|
||||
// calls.
|
||||
func (c *Compiler) deferInitFunc(frame *Frame) {
|
||||
func (b *builder) deferInitFunc() {
|
||||
// Some setup.
|
||||
frame.deferFuncs = make(map[*ir.Function]int)
|
||||
frame.deferInvokeFuncs = make(map[string]int)
|
||||
frame.deferClosureFuncs = make(map[*ir.Function]int)
|
||||
b.deferFuncs = make(map[*ssa.Function]int)
|
||||
b.deferInvokeFuncs = make(map[string]int)
|
||||
b.deferClosureFuncs = make(map[*ssa.Function]int)
|
||||
b.deferExprFuncs = make(map[ssa.Value]int)
|
||||
b.deferBuiltinFuncs = make(map[ssa.Value]deferBuiltin)
|
||||
|
||||
// Create defer list pointer.
|
||||
deferType := llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)
|
||||
frame.deferPtr = c.builder.CreateAlloca(deferType, "deferPtr")
|
||||
c.builder.CreateStore(llvm.ConstPointerNull(deferType), frame.deferPtr)
|
||||
deferType := llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)
|
||||
b.deferPtr = b.CreateAlloca(deferType, "deferPtr")
|
||||
b.CreateStore(llvm.ConstPointerNull(deferType), b.deferPtr)
|
||||
}
|
||||
|
||||
// isInLoop checks if there is a path from a basic block to itself.
|
||||
@@ -69,53 +72,52 @@ func isInLoop(start *ssa.BasicBlock) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// emitDefer emits a single defer instruction, to be run when this function
|
||||
// createDefer emits a single defer instruction, to be run when this function
|
||||
// returns.
|
||||
func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
||||
func (b *builder) createDefer(instr *ssa.Defer) {
|
||||
// The pointer to the previous defer struct, which we will replace to
|
||||
// make a linked list.
|
||||
next := c.builder.CreateLoad(frame.deferPtr, "defer.next")
|
||||
next := b.CreateLoad(b.deferPtr, "defer.next")
|
||||
|
||||
var values []llvm.Value
|
||||
valueTypes := []llvm.Type{c.uintptrType, next.Type()}
|
||||
valueTypes := []llvm.Type{b.uintptrType, next.Type()}
|
||||
if instr.Call.IsInvoke() {
|
||||
// Method call on an interface.
|
||||
|
||||
// Get callback type number.
|
||||
methodName := instr.Call.Method.FullName()
|
||||
if _, ok := frame.deferInvokeFuncs[methodName]; !ok {
|
||||
frame.deferInvokeFuncs[methodName] = len(frame.allDeferFuncs)
|
||||
frame.allDeferFuncs = append(frame.allDeferFuncs, &instr.Call)
|
||||
if _, ok := b.deferInvokeFuncs[methodName]; !ok {
|
||||
b.deferInvokeFuncs[methodName] = len(b.allDeferFuncs)
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
|
||||
}
|
||||
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferInvokeFuncs[methodName]), false)
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferInvokeFuncs[methodName]), false)
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields, followed by the call parameters).
|
||||
itf := c.getValue(frame, instr.Call.Value) // interface
|
||||
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||
values = []llvm.Value{callback, next, receiverValue}
|
||||
valueTypes = append(valueTypes, c.i8ptrType)
|
||||
itf := b.getValue(instr.Call.Value) // interface
|
||||
typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode")
|
||||
receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||
values = []llvm.Value{callback, next, typecode, receiverValue}
|
||||
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
|
||||
for _, arg := range instr.Call.Args {
|
||||
val := c.getValue(frame, arg)
|
||||
val := b.getValue(arg)
|
||||
values = append(values, val)
|
||||
valueTypes = append(valueTypes, val.Type())
|
||||
}
|
||||
|
||||
} else if callee, ok := instr.Call.Value.(*ssa.Function); ok {
|
||||
// Regular function call.
|
||||
fn := c.ir.GetFunction(callee)
|
||||
|
||||
if _, ok := frame.deferFuncs[fn]; !ok {
|
||||
frame.deferFuncs[fn] = len(frame.allDeferFuncs)
|
||||
frame.allDeferFuncs = append(frame.allDeferFuncs, fn)
|
||||
if _, ok := b.deferFuncs[callee]; !ok {
|
||||
b.deferFuncs[callee] = len(b.allDeferFuncs)
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, callee)
|
||||
}
|
||||
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferFuncs[fn]), false)
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferFuncs[callee]), false)
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields).
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam := c.getValue(frame, param)
|
||||
llvmParam := b.getValue(param)
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
}
|
||||
@@ -127,65 +129,109 @@ func (c *Compiler) emitDefer(frame *Frame, instr *ssa.Defer) {
|
||||
// pointer.
|
||||
// TODO: ignore this closure entirely and put pointers to the free
|
||||
// variables directly in the defer struct, avoiding a memory allocation.
|
||||
closure := c.getValue(frame, instr.Call.Value)
|
||||
context := c.builder.CreateExtractValue(closure, 0, "")
|
||||
closure := b.getValue(instr.Call.Value)
|
||||
context := b.CreateExtractValue(closure, 0, "")
|
||||
|
||||
// Get the callback number.
|
||||
fn := c.ir.GetFunction(makeClosure.Fn.(*ssa.Function))
|
||||
if _, ok := frame.deferClosureFuncs[fn]; !ok {
|
||||
frame.deferClosureFuncs[fn] = len(frame.allDeferFuncs)
|
||||
frame.allDeferFuncs = append(frame.allDeferFuncs, makeClosure)
|
||||
fn := makeClosure.Fn.(*ssa.Function)
|
||||
if _, ok := b.deferClosureFuncs[fn]; !ok {
|
||||
b.deferClosureFuncs[fn] = len(b.allDeferFuncs)
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, makeClosure)
|
||||
}
|
||||
callback := llvm.ConstInt(c.uintptrType, uint64(frame.deferClosureFuncs[fn]), false)
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferClosureFuncs[fn]), false)
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields, followed by all parameters including the
|
||||
// context pointer).
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam := c.getValue(frame, param)
|
||||
llvmParam := b.getValue(param)
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
}
|
||||
values = append(values, context)
|
||||
valueTypes = append(valueTypes, context.Type())
|
||||
|
||||
} else if builtin, ok := instr.Call.Value.(*ssa.Builtin); ok {
|
||||
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))
|
||||
}
|
||||
|
||||
if _, ok := b.deferBuiltinFuncs[instr.Call.Value]; !ok {
|
||||
b.deferBuiltinFuncs[instr.Call.Value] = deferBuiltin{
|
||||
callName: builtin.Name(),
|
||||
pos: builtin.Pos(),
|
||||
argTypes: argTypes,
|
||||
callback: len(b.allDeferFuncs),
|
||||
}
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, instr.Call.Value)
|
||||
}
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferBuiltinFuncs[instr.Call.Value].callback), false)
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields).
|
||||
values = []llvm.Value{callback, next}
|
||||
for _, param := range argValues {
|
||||
values = append(values, param)
|
||||
valueTypes = append(valueTypes, param.Type())
|
||||
}
|
||||
|
||||
} else {
|
||||
c.addError(instr.Pos(), "todo: defer on uncommon function call type")
|
||||
return
|
||||
funcValue := b.getValue(instr.Call.Value)
|
||||
|
||||
if _, ok := b.deferExprFuncs[instr.Call.Value]; !ok {
|
||||
b.deferExprFuncs[instr.Call.Value] = len(b.allDeferFuncs)
|
||||
b.allDeferFuncs = append(b.allDeferFuncs, &instr.Call)
|
||||
}
|
||||
|
||||
callback := llvm.ConstInt(b.uintptrType, uint64(b.deferExprFuncs[instr.Call.Value]), false)
|
||||
|
||||
// Collect all values to be put in the struct (starting with
|
||||
// runtime._defer fields, followed by all parameters including the
|
||||
// context pointer).
|
||||
values = []llvm.Value{callback, next, funcValue}
|
||||
valueTypes = append(valueTypes, funcValue.Type())
|
||||
for _, param := range instr.Call.Args {
|
||||
llvmParam := b.getValue(param)
|
||||
values = append(values, llvmParam)
|
||||
valueTypes = append(valueTypes, llvmParam.Type())
|
||||
}
|
||||
}
|
||||
|
||||
// Make a struct out of the collected values to put in the defer frame.
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||
deferFrame := llvm.ConstNull(deferFrameType)
|
||||
for i, value := range values {
|
||||
deferFrame = c.builder.CreateInsertValue(deferFrame, value, i, "")
|
||||
deferFrame = b.CreateInsertValue(deferFrame, value, i, "")
|
||||
}
|
||||
|
||||
// Put this struct in an allocation.
|
||||
var alloca llvm.Value
|
||||
if !isInLoop(instr.Block()) {
|
||||
// This can safely use a stack allocation.
|
||||
alloca = llvmutil.CreateEntryBlockAlloca(c.builder, deferFrameType, "defer.alloca")
|
||||
alloca = llvmutil.CreateEntryBlockAlloca(b.Builder, deferFrameType, "defer.alloca")
|
||||
} else {
|
||||
// This may be hit a variable number of times, so use a heap allocation.
|
||||
size := c.targetData.TypeAllocSize(deferFrameType)
|
||||
sizeValue := llvm.ConstInt(c.uintptrType, size, false)
|
||||
allocCall := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
|
||||
alloca = c.builder.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
|
||||
size := b.targetData.TypeAllocSize(deferFrameType)
|
||||
sizeValue := llvm.ConstInt(b.uintptrType, size, false)
|
||||
allocCall := b.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "defer.alloc.call")
|
||||
alloca = b.CreateBitCast(allocCall, llvm.PointerType(deferFrameType, 0), "defer.alloc")
|
||||
}
|
||||
if c.NeedsStackObjects() {
|
||||
c.trackPointer(alloca)
|
||||
if b.NeedsStackObjects {
|
||||
b.trackPointer(alloca)
|
||||
}
|
||||
c.builder.CreateStore(deferFrame, alloca)
|
||||
b.CreateStore(deferFrame, alloca)
|
||||
|
||||
// Push it on top of the linked list by replacing deferPtr.
|
||||
allocaCast := c.builder.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||
c.builder.CreateStore(allocaCast, frame.deferPtr)
|
||||
allocaCast := b.CreateBitCast(alloca, next.Type(), "defer.alloca.cast")
|
||||
b.CreateStore(allocaCast, b.deferPtr)
|
||||
}
|
||||
|
||||
// emitRunDefers emits code to run all deferred functions.
|
||||
func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
// createRunDefers emits code to run all deferred functions.
|
||||
func (b *builder) createRunDefers() {
|
||||
// Add a loop like the following:
|
||||
// for stack != nil {
|
||||
// _stack := stack
|
||||
@@ -202,151 +248,208 @@ func (c *Compiler) emitRunDefers(frame *Frame) {
|
||||
// }
|
||||
|
||||
// Create loop.
|
||||
loophead := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loophead")
|
||||
loop := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.loop")
|
||||
unreachable := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.default")
|
||||
end := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.end")
|
||||
c.builder.CreateBr(loophead)
|
||||
loophead := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loophead")
|
||||
loop := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.loop")
|
||||
unreachable := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.default")
|
||||
end := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.end")
|
||||
b.CreateBr(loophead)
|
||||
|
||||
// Create loop head:
|
||||
// for stack != nil {
|
||||
c.builder.SetInsertPointAtEnd(loophead)
|
||||
deferData := c.builder.CreateLoad(frame.deferPtr, "")
|
||||
stackIsNil := c.builder.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
|
||||
c.builder.CreateCondBr(stackIsNil, end, loop)
|
||||
b.SetInsertPointAtEnd(loophead)
|
||||
deferData := b.CreateLoad(b.deferPtr, "")
|
||||
stackIsNil := b.CreateICmp(llvm.IntEQ, deferData, llvm.ConstPointerNull(deferData.Type()), "stackIsNil")
|
||||
b.CreateCondBr(stackIsNil, end, loop)
|
||||
|
||||
// Create loop body:
|
||||
// _stack := stack
|
||||
// stack = stack.next
|
||||
// switch stack.callback {
|
||||
c.builder.SetInsertPointAtEnd(loop)
|
||||
nextStackGEP := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 1, false), // .next field
|
||||
b.SetInsertPointAtEnd(loop)
|
||||
nextStackGEP := b.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 1, false), // .next field
|
||||
}, "stack.next.gep")
|
||||
nextStack := c.builder.CreateLoad(nextStackGEP, "stack.next")
|
||||
c.builder.CreateStore(nextStack, frame.deferPtr)
|
||||
gep := c.builder.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(c.ctx.Int32Type(), 0, false), // .callback field
|
||||
nextStack := b.CreateLoad(nextStackGEP, "stack.next")
|
||||
b.CreateStore(nextStack, b.deferPtr)
|
||||
gep := b.CreateInBoundsGEP(deferData, []llvm.Value{
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(b.ctx.Int32Type(), 0, false), // .callback field
|
||||
}, "callback.gep")
|
||||
callback := c.builder.CreateLoad(gep, "callback")
|
||||
sw := c.builder.CreateSwitch(callback, unreachable, len(frame.allDeferFuncs))
|
||||
callback := b.CreateLoad(gep, "callback")
|
||||
sw := b.CreateSwitch(callback, unreachable, len(b.allDeferFuncs))
|
||||
|
||||
for i, callback := range frame.allDeferFuncs {
|
||||
for i, callback := range b.allDeferFuncs {
|
||||
// Create switch case, for example:
|
||||
// case 0:
|
||||
// // run first deferred call
|
||||
block := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "rundefers.callback")
|
||||
sw.AddCase(llvm.ConstInt(c.uintptrType, uint64(i), false), block)
|
||||
c.builder.SetInsertPointAtEnd(block)
|
||||
block := b.ctx.AddBasicBlock(b.llvmFn, "rundefers.callback")
|
||||
sw.AddCase(llvm.ConstInt(b.uintptrType, uint64(i), false), block)
|
||||
b.SetInsertPointAtEnd(block)
|
||||
switch callback := callback.(type) {
|
||||
case *ssa.CallCommon:
|
||||
// Call on an interface value.
|
||||
if !callback.IsInvoke() {
|
||||
panic("expected an invoke call, not a direct call")
|
||||
}
|
||||
// Call on an value or interface value.
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0), c.i8ptrType}
|
||||
for _, arg := range callback.Args {
|
||||
valueTypes = append(valueTypes, c.getLLVMType(arg.Type()))
|
||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||
|
||||
if !callback.IsInvoke() {
|
||||
//Expect funcValue to be passed through the defer frame.
|
||||
valueTypes = append(valueTypes, b.getFuncType(callback.Signature()))
|
||||
} else {
|
||||
//Expect typecode
|
||||
valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType)
|
||||
}
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
|
||||
for _, arg := range callback.Args {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(arg.Type()))
|
||||
}
|
||||
|
||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
|
||||
// Extract the params from the struct (including receiver).
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := 2; i < len(valueTypes); i++ {
|
||||
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "gep")
|
||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "gep")
|
||||
forwardParam := b.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
|
||||
// Add the context parameter. An interface call cannot also be a
|
||||
// closure but we have to supply the parameter anyway for platforms
|
||||
// with a strict calling convention.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
var fnPtr llvm.Value
|
||||
|
||||
if !callback.IsInvoke() {
|
||||
// Isolate the func value.
|
||||
funcValue := forwardParams[0]
|
||||
forwardParams = forwardParams[1:]
|
||||
|
||||
//Get function pointer and context
|
||||
fp, context := b.decodeFuncValue(funcValue, callback.Signature())
|
||||
fnPtr = fp
|
||||
|
||||
//Pass context
|
||||
forwardParams = append(forwardParams, context)
|
||||
} else {
|
||||
// Isolate the typecode.
|
||||
typecode := forwardParams[0]
|
||||
forwardParams = forwardParams[1:]
|
||||
fnPtr = b.getInvokePtr(callback, typecode)
|
||||
|
||||
// Add the context parameter. An interface call cannot also be a
|
||||
// closure but we have to supply the parameter anyway for platforms
|
||||
// with a strict calling convention.
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
}
|
||||
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
|
||||
fnPtr, _ := c.getInvokeCall(frame, callback)
|
||||
c.createCall(fnPtr, forwardParams, "")
|
||||
b.createCall(fnPtr, forwardParams, "")
|
||||
|
||||
case *ir.Function:
|
||||
case *ssa.Function:
|
||||
// Direct call.
|
||||
|
||||
// Get the real defer struct type and cast to it.
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
|
||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||
for _, param := range callback.Params {
|
||||
valueTypes = append(valueTypes, c.getLLVMType(param.Type()))
|
||||
valueTypes = append(valueTypes, b.getLLVMType(param.Type()))
|
||||
}
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
|
||||
// Extract the params from the struct.
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := range callback.Params {
|
||||
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||
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)
|
||||
}
|
||||
|
||||
// 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(c.i8ptrType))
|
||||
// Plain TinyGo functions add some extra parameters to implement async functionality and function recievers.
|
||||
// These parameters should not be supplied when calling into an external C/ASM function.
|
||||
if !b.getFunctionInfo(callback).exported {
|
||||
// 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(c.i8ptrType))
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
}
|
||||
|
||||
// Call real function.
|
||||
c.createCall(callback.LLVMFn, forwardParams, "")
|
||||
b.createCall(b.getFunction(callback), forwardParams, "")
|
||||
|
||||
case *ssa.MakeClosure:
|
||||
// Get the real defer struct type and cast to it.
|
||||
fn := c.ir.GetFunction(callback.Fn.(*ssa.Function))
|
||||
valueTypes := []llvm.Type{c.uintptrType, llvm.PointerType(c.getLLVMRuntimeType("_defer"), 0)}
|
||||
fn := callback.Fn.(*ssa.Function)
|
||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||
params := fn.Signature.Params()
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
valueTypes = append(valueTypes, c.getLLVMType(params.At(i).Type()))
|
||||
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
||||
}
|
||||
valueTypes = append(valueTypes, c.i8ptrType) // closure
|
||||
deferFrameType := c.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := c.builder.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
valueTypes = append(valueTypes, b.i8ptrType) // closure
|
||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
|
||||
// Extract the params from the struct.
|
||||
forwardParams := []llvm.Value{}
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := 2; i < len(valueTypes); i++ {
|
||||
gep := c.builder.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(c.ctx.Int32Type(), uint64(i), false)}, "")
|
||||
forwardParam := c.builder.CreateLoad(gep, "param")
|
||||
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i), false)}, "")
|
||||
forwardParam := b.CreateLoad(gep, "param")
|
||||
forwardParams = append(forwardParams, forwardParam)
|
||||
}
|
||||
|
||||
// Parent coroutine handle.
|
||||
forwardParams = append(forwardParams, llvm.Undef(c.i8ptrType))
|
||||
forwardParams = append(forwardParams, llvm.Undef(b.i8ptrType))
|
||||
|
||||
// Call deferred function.
|
||||
c.createCall(fn.LLVMFn, forwardParams, "")
|
||||
b.createCall(b.getFunction(fn), forwardParams, "")
|
||||
case *ssa.Builtin:
|
||||
db := b.deferBuiltinFuncs[callback]
|
||||
|
||||
//Get parameter types
|
||||
valueTypes := []llvm.Type{b.uintptrType, llvm.PointerType(b.getLLVMRuntimeType("_defer"), 0)}
|
||||
|
||||
//Get signature from call results
|
||||
params := callback.Type().Underlying().(*types.Signature).Params()
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
valueTypes = append(valueTypes, b.getLLVMType(params.At(i).Type()))
|
||||
}
|
||||
|
||||
deferFrameType := b.ctx.StructType(valueTypes, false)
|
||||
deferFramePtr := b.CreateBitCast(deferData, llvm.PointerType(deferFrameType, 0), "deferFrame")
|
||||
|
||||
// Extract the params from the struct.
|
||||
var argValues []llvm.Value
|
||||
zero := llvm.ConstInt(b.ctx.Int32Type(), 0, false)
|
||||
for i := 0; i < params.Len(); i++ {
|
||||
gep := b.CreateInBoundsGEP(deferFramePtr, []llvm.Value{zero, llvm.ConstInt(b.ctx.Int32Type(), uint64(i+2), false)}, "gep")
|
||||
forwardParam := b.CreateLoad(gep, "param")
|
||||
argValues = append(argValues, forwardParam)
|
||||
}
|
||||
|
||||
_, err := b.createBuiltin(db.argTypes, argValues, db.callName, db.pos)
|
||||
if err != nil {
|
||||
b.diagnostics = append(b.diagnostics, err)
|
||||
}
|
||||
default:
|
||||
panic("unknown deferred function type")
|
||||
}
|
||||
|
||||
// Branch back to the start of the loop.
|
||||
c.builder.CreateBr(loophead)
|
||||
b.CreateBr(loophead)
|
||||
}
|
||||
|
||||
// Create default unreachable block:
|
||||
// default:
|
||||
// unreachable
|
||||
// }
|
||||
c.builder.SetInsertPointAtEnd(unreachable)
|
||||
c.builder.CreateUnreachable()
|
||||
b.SetInsertPointAtEnd(unreachable)
|
||||
b.CreateUnreachable()
|
||||
|
||||
// End of loop.
|
||||
c.builder.SetInsertPointAtEnd(end)
|
||||
b.SetInsertPointAtEnd(end)
|
||||
}
|
||||
|
||||
+6
-3
@@ -1,5 +1,7 @@
|
||||
package compiler
|
||||
|
||||
// This file contains some utility functions related to error handling.
|
||||
|
||||
import (
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
@@ -9,15 +11,16 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) makeError(pos token.Pos, msg string) types.Error {
|
||||
// makeError makes it easy to create an error from a token.Pos with a message.
|
||||
func (c *compilerContext) makeError(pos token.Pos, msg string) types.Error {
|
||||
return types.Error{
|
||||
Fset: c.ir.Program.Fset,
|
||||
Fset: c.program.Fset,
|
||||
Pos: pos,
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) addError(pos token.Pos, msg string) {
|
||||
func (c *compilerContext) addError(pos token.Pos, msg string) {
|
||||
c.diagnostics = append(c.diagnostics, c.makeError(pos, msg))
|
||||
}
|
||||
|
||||
|
||||
+40
-64
@@ -10,49 +10,21 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type funcValueImplementation int
|
||||
|
||||
const (
|
||||
funcValueNone funcValueImplementation = iota
|
||||
|
||||
// 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.
|
||||
funcValueDoubleword
|
||||
|
||||
// As funcValueDoubleword, 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.
|
||||
funcValueSwitch
|
||||
)
|
||||
|
||||
// funcImplementation picks an appropriate func value implementation for the
|
||||
// target.
|
||||
func (c *Compiler) funcImplementation() funcValueImplementation {
|
||||
// Always pick the switch implementation, as it allows the use of blocking
|
||||
// inside a function that is used as a func value.
|
||||
switch c.Scheduler() {
|
||||
case "coroutines":
|
||||
return funcValueSwitch
|
||||
case "tasks":
|
||||
return funcValueDoubleword
|
||||
default:
|
||||
panic("unknown scheduler type")
|
||||
}
|
||||
// createFuncValue creates a function value from a raw function pointer with no
|
||||
// context.
|
||||
func (b *builder) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
||||
return b.compilerContext.createFuncValue(b.Builder, funcPtr, context, sig)
|
||||
}
|
||||
|
||||
// createFuncValue creates a function value from a raw function pointer with no
|
||||
// context.
|
||||
func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
||||
func (c *compilerContext) createFuncValue(builder llvm.Builder, funcPtr, context llvm.Value, sig *types.Signature) llvm.Value {
|
||||
var funcValueScalar llvm.Value
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
switch c.FuncImplementation {
|
||||
case "doubleword":
|
||||
// Closure is: {context, function pointer}
|
||||
funcValueScalar = funcPtr
|
||||
case funcValueSwitch:
|
||||
case "switch":
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
funcValueWithSignatureGlobalName := funcPtr.Name() + "$withSignature"
|
||||
funcValueWithSignatureGlobal := c.mod.NamedGlobal(funcValueWithSignatureGlobalName)
|
||||
@@ -73,35 +45,35 @@ func (c *Compiler) createFuncValue(funcPtr, context llvm.Value, sig *types.Signa
|
||||
}
|
||||
funcValueType := c.getFuncType(sig)
|
||||
funcValue := llvm.Undef(funcValueType)
|
||||
funcValue = c.builder.CreateInsertValue(funcValue, context, 0, "")
|
||||
funcValue = c.builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
|
||||
funcValue = builder.CreateInsertValue(funcValue, context, 0, "")
|
||||
funcValue = builder.CreateInsertValue(funcValue, funcValueScalar, 1, "")
|
||||
return funcValue
|
||||
}
|
||||
|
||||
// extractFuncScalar returns some scalar that can be used in comparisons. It is
|
||||
// a cheap operation.
|
||||
func (c *Compiler) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
||||
return c.builder.CreateExtractValue(funcValue, 1, "")
|
||||
func (b *builder) extractFuncScalar(funcValue llvm.Value) llvm.Value {
|
||||
return b.CreateExtractValue(funcValue, 1, "")
|
||||
}
|
||||
|
||||
// extractFuncContext extracts the context pointer from this function value. It
|
||||
// is a cheap operation.
|
||||
func (c *Compiler) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
||||
return c.builder.CreateExtractValue(funcValue, 0, "")
|
||||
func (b *builder) extractFuncContext(funcValue llvm.Value) llvm.Value {
|
||||
return b.CreateExtractValue(funcValue, 0, "")
|
||||
}
|
||||
|
||||
// decodeFuncValue extracts the context and the function pointer from this func
|
||||
// value. This may be an expensive operation.
|
||||
func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (funcPtr, context llvm.Value) {
|
||||
context = c.builder.CreateExtractValue(funcValue, 0, "")
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
funcPtr = c.builder.CreateExtractValue(funcValue, 1, "")
|
||||
case funcValueSwitch:
|
||||
llvmSig := c.getRawFuncType(sig)
|
||||
sigGlobal := c.getTypeCode(sig)
|
||||
funcPtr = c.createRuntimeCall("getFuncPtr", []llvm.Value{funcValue, sigGlobal}, "")
|
||||
funcPtr = c.builder.CreateIntToPtr(funcPtr, llvmSig, "")
|
||||
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")
|
||||
}
|
||||
@@ -109,12 +81,12 @@ func (c *Compiler) decodeFuncValue(funcValue llvm.Value, sig *types.Signature) (
|
||||
}
|
||||
|
||||
// getFuncType returns the type of a func value given a signature.
|
||||
func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
|
||||
switch c.funcImplementation() {
|
||||
case funcValueDoubleword:
|
||||
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 funcValueSwitch:
|
||||
case "switch":
|
||||
return c.getLLVMRuntimeType("funcValue")
|
||||
default:
|
||||
panic("unimplemented func value variant")
|
||||
@@ -122,7 +94,7 @@ func (c *Compiler) getFuncType(typ *types.Signature) llvm.Type {
|
||||
}
|
||||
|
||||
// getRawFuncType returns a LLVM function pointer type for a given signature.
|
||||
func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
|
||||
func (c *compilerContext) getRawFuncType(typ *types.Signature) llvm.Type {
|
||||
// Get the return type.
|
||||
var returnType llvm.Type
|
||||
switch typ.Results().Len() {
|
||||
@@ -152,11 +124,15 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
|
||||
// The receiver is not an interface, but a i8* type.
|
||||
recv = c.i8ptrType
|
||||
}
|
||||
paramTypes = append(paramTypes, c.expandFormalParamType(recv)...)
|
||||
for _, info := range expandFormalParamType(recv, "", nil) {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
}
|
||||
for i := 0; i < typ.Params().Len(); i++ {
|
||||
subType := c.getLLVMType(typ.Params().At(i).Type())
|
||||
paramTypes = append(paramTypes, c.expandFormalParamType(subType)...)
|
||||
for _, info := range expandFormalParamType(subType, "", nil) {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
}
|
||||
// All functions take these parameters at the end.
|
||||
paramTypes = append(paramTypes, c.i8ptrType) // context
|
||||
@@ -168,24 +144,24 @@ func (c *Compiler) getRawFuncType(typ *types.Signature) llvm.Type {
|
||||
|
||||
// parseMakeClosure makes a function value (with context) from the given
|
||||
// closure expression.
|
||||
func (c *Compiler) parseMakeClosure(frame *Frame, expr *ssa.MakeClosure) (llvm.Value, error) {
|
||||
func (b *builder) parseMakeClosure(expr *ssa.MakeClosure) (llvm.Value, error) {
|
||||
if len(expr.Bindings) == 0 {
|
||||
panic("unexpected: MakeClosure without bound variables")
|
||||
}
|
||||
f := c.ir.GetFunction(expr.Fn.(*ssa.Function))
|
||||
f := expr.Fn.(*ssa.Function)
|
||||
|
||||
// Collect all bound variables.
|
||||
boundVars := make([]llvm.Value, len(expr.Bindings))
|
||||
for i, binding := range expr.Bindings {
|
||||
// The context stores the bound variables.
|
||||
llvmBoundVar := c.getValue(frame, binding)
|
||||
llvmBoundVar := b.getValue(binding)
|
||||
boundVars[i] = llvmBoundVar
|
||||
}
|
||||
|
||||
// Store the bound variables in a single object, allocating it on the heap
|
||||
// if necessary.
|
||||
context := c.emitPointerPack(boundVars)
|
||||
context := b.emitPointerPack(boundVars)
|
||||
|
||||
// Create the closure.
|
||||
return c.createFuncValue(f.LLVMFn, context, f.Signature), nil
|
||||
return b.createFuncValue(b.getFunction(f), context, f.Signature), nil
|
||||
}
|
||||
|
||||
+23
-17
@@ -12,53 +12,59 @@ import (
|
||||
|
||||
// trackExpr inserts pointer tracking intrinsics for the GC if the expression is
|
||||
// one of the expressions that need this.
|
||||
func (c *Compiler) trackExpr(frame *Frame, expr ssa.Value, value llvm.Value) {
|
||||
func (b *builder) trackExpr(expr ssa.Value, value llvm.Value) {
|
||||
// There are uses of this expression, Make sure the pointers
|
||||
// are tracked during GC.
|
||||
switch expr := expr.(type) {
|
||||
case *ssa.Alloc, *ssa.MakeChan, *ssa.MakeMap:
|
||||
// These values are always of pointer type in IR.
|
||||
c.trackPointer(value)
|
||||
b.trackPointer(value)
|
||||
case *ssa.Call, *ssa.Convert, *ssa.MakeClosure, *ssa.MakeInterface, *ssa.MakeSlice, *ssa.Next:
|
||||
if !value.IsNil() {
|
||||
c.trackValue(value)
|
||||
b.trackValue(value)
|
||||
}
|
||||
case *ssa.Select:
|
||||
if alloca, ok := frame.selectRecvBuf[expr]; ok {
|
||||
if alloca, ok := b.selectRecvBuf[expr]; ok {
|
||||
if alloca.IsAUndefValue().IsNil() {
|
||||
c.trackPointer(alloca)
|
||||
b.trackPointer(alloca)
|
||||
}
|
||||
}
|
||||
case *ssa.UnOp:
|
||||
switch expr.Op {
|
||||
case token.MUL:
|
||||
// Pointer dereference.
|
||||
c.trackValue(value)
|
||||
b.trackValue(value)
|
||||
case token.ARROW:
|
||||
// Channel receive operator.
|
||||
// It's not necessary to look at commaOk here, because in that
|
||||
// case it's just an aggregate and trackValue will extract the
|
||||
// pointer in there (if there is one).
|
||||
c.trackValue(value)
|
||||
b.trackValue(value)
|
||||
}
|
||||
case *ssa.BinOp:
|
||||
switch expr.Op {
|
||||
case token.ADD:
|
||||
// String concatenation.
|
||||
b.trackValue(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// trackValue locates pointers in a value (possibly an aggregate) and tracks the
|
||||
// individual pointers
|
||||
func (c *Compiler) trackValue(value llvm.Value) {
|
||||
func (b *builder) trackValue(value llvm.Value) {
|
||||
typ := value.Type()
|
||||
switch typ.TypeKind() {
|
||||
case llvm.PointerTypeKind:
|
||||
c.trackPointer(value)
|
||||
b.trackPointer(value)
|
||||
case llvm.StructTypeKind:
|
||||
if !typeHasPointers(typ) {
|
||||
return
|
||||
}
|
||||
numElements := typ.StructElementTypesCount()
|
||||
for i := 0; i < numElements; i++ {
|
||||
subValue := c.builder.CreateExtractValue(value, i, "")
|
||||
c.trackValue(subValue)
|
||||
subValue := b.CreateExtractValue(value, i, "")
|
||||
b.trackValue(subValue)
|
||||
}
|
||||
case llvm.ArrayTypeKind:
|
||||
if !typeHasPointers(typ) {
|
||||
@@ -66,19 +72,19 @@ func (c *Compiler) trackValue(value llvm.Value) {
|
||||
}
|
||||
numElements := typ.ArrayLength()
|
||||
for i := 0; i < numElements; i++ {
|
||||
subValue := c.builder.CreateExtractValue(value, i, "")
|
||||
c.trackValue(subValue)
|
||||
subValue := b.CreateExtractValue(value, i, "")
|
||||
b.trackValue(subValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// trackPointer creates a call to runtime.trackPointer, bitcasting the poitner
|
||||
// first if needed. The input value must be of LLVM pointer type.
|
||||
func (c *Compiler) trackPointer(value llvm.Value) {
|
||||
if value.Type() != c.i8ptrType {
|
||||
value = c.builder.CreateBitCast(value, c.i8ptrType, "")
|
||||
func (b *builder) trackPointer(value llvm.Value) {
|
||||
if value.Type() != b.i8ptrType {
|
||||
value = b.CreateBitCast(value, b.i8ptrType, "")
|
||||
}
|
||||
c.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
|
||||
b.createRuntimeCall("trackPointer", []llvm.Value{value}, "")
|
||||
}
|
||||
|
||||
// typeHasPointers returns whether this type is a pointer or contains pointers.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+93
-37
@@ -3,34 +3,49 @@ package compiler
|
||||
// This file implements the 'go' keyword to start a new goroutine. See
|
||||
// goroutine-lowering.go for more details.
|
||||
|
||||
import "tinygo.org/x/go-llvm"
|
||||
import (
|
||||
"go/token"
|
||||
|
||||
// emitStartGoroutine starts a new goroutine with the provided function pointer
|
||||
"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 (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) llvm.Value {
|
||||
switch c.Scheduler() {
|
||||
case "tasks":
|
||||
paramBundle := c.emitPointerPack(params)
|
||||
paramBundle = c.builder.CreatePtrToInt(paramBundle, c.uintptrType, "")
|
||||
|
||||
calleeValue := c.createGoroutineStartWrapper(funcPtr)
|
||||
c.createRuntimeCall("startGoroutine", []llvm.Value{calleeValue, paramBundle}, "")
|
||||
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)
|
||||
}
|
||||
case "coroutines":
|
||||
// We roundtrip through runtime.makeGoroutine as a signal (to find these
|
||||
// calls) and to break any optimizations LLVM will try to do: they are
|
||||
// invalid if we called this as a regular function to be updated later.
|
||||
calleeValue := c.builder.CreatePtrToInt(funcPtr, c.uintptrType, "")
|
||||
calleeValue = c.createRuntimeCall("makeGoroutine", []llvm.Value{calleeValue}, "")
|
||||
calleeValue = c.builder.CreateIntToPtr(calleeValue, funcPtr.Type(), "")
|
||||
c.createCall(calleeValue, append(params, llvm.ConstPointerNull(c.i8ptrType)), "")
|
||||
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")
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
@@ -52,36 +67,58 @@ func (c *Compiler) emitStartGoroutine(funcPtr llvm.Value, params []llvm.Value) l
|
||||
// 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 *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
|
||||
func (c *compilerContext) createGoroutineStartWrapper(fn llvm.Value, prefix string, pos token.Pos) llvm.Value {
|
||||
var wrapper llvm.Value
|
||||
|
||||
builder := c.ctx.NewBuilder()
|
||||
defer builder.Dispose()
|
||||
|
||||
if !fn.IsAFunction().IsNil() {
|
||||
// See whether this wrapper has already been created. If so, return it.
|
||||
name := fn.Name()
|
||||
wrapper = c.mod.NamedFunction(name + "$gowrapper")
|
||||
if !wrapper.IsNil() {
|
||||
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
|
||||
return llvm.ConstPtrToInt(wrapper, c.uintptrType)
|
||||
}
|
||||
|
||||
// Save the current position in the IR builder.
|
||||
currentBlock := c.builder.GetInsertBlock()
|
||||
defer c.builder.SetInsertPointAtEnd(currentBlock)
|
||||
|
||||
// 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.PrivateLinkage)
|
||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", name))
|
||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
c.builder.SetInsertPointAtEnd(entry)
|
||||
builder.SetInsertPointAtEnd(entry)
|
||||
|
||||
if c.Debug {
|
||||
pos := c.program.Fset.Position(pos)
|
||||
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
||||
File: c.getDIFile(pos.Filename),
|
||||
Parameters: nil, // do not show parameters in debugger
|
||||
Flags: 0, // ?
|
||||
})
|
||||
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
|
||||
Name: "<goroutine wrapper>",
|
||||
File: c.getDIFile(pos.Filename),
|
||||
Line: pos.Line,
|
||||
Type: diFuncType,
|
||||
LocalToUnit: true,
|
||||
IsDefinition: true,
|
||||
ScopeLine: 0,
|
||||
Flags: llvm.FlagPrototyped,
|
||||
Optimized: true,
|
||||
})
|
||||
wrapper.SetSubprogram(difunc)
|
||||
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
}
|
||||
|
||||
// Create the list of params for the call.
|
||||
paramTypes := fn.Type().ElementType().ParamTypes()
|
||||
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes[:len(paramTypes)-1])
|
||||
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes[:len(paramTypes)-1])
|
||||
params = append(params, llvm.Undef(c.i8ptrType))
|
||||
|
||||
// Create the call.
|
||||
c.builder.CreateCall(fn, params, "")
|
||||
builder.CreateCall(fn, params, "")
|
||||
|
||||
} else {
|
||||
// For a function pointer like this:
|
||||
@@ -101,22 +138,41 @@ func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
|
||||
// With a bit of luck, identical wrapper functions like these can be
|
||||
// merged into one.
|
||||
|
||||
// Save the current position in the IR builder.
|
||||
currentBlock := c.builder.GetInsertBlock()
|
||||
defer c.builder.SetInsertPointAtEnd(currentBlock)
|
||||
|
||||
// Create the wrapper.
|
||||
wrapperType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.i8ptrType}, false)
|
||||
wrapper = llvm.AddFunction(c.mod, ".gowrapper", wrapperType)
|
||||
wrapper = llvm.AddFunction(c.mod, prefix+".gowrapper", wrapperType)
|
||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
wrapper.AddAttributeAtIndex(-1, c.ctx.CreateStringAttribute("tinygo-gowrapper", ""))
|
||||
entry := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
c.builder.SetInsertPointAtEnd(entry)
|
||||
builder.SetInsertPointAtEnd(entry)
|
||||
|
||||
if c.Debug {
|
||||
pos := c.program.Fset.Position(pos)
|
||||
diFuncType := c.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
||||
File: c.getDIFile(pos.Filename),
|
||||
Parameters: nil, // do not show parameters in debugger
|
||||
Flags: 0, // ?
|
||||
})
|
||||
difunc := c.dibuilder.CreateFunction(c.getDIFile(pos.Filename), llvm.DIFunction{
|
||||
Name: "<goroutine wrapper>",
|
||||
File: c.getDIFile(pos.Filename),
|
||||
Line: pos.Line,
|
||||
Type: diFuncType,
|
||||
LocalToUnit: true,
|
||||
IsDefinition: true,
|
||||
ScopeLine: 0,
|
||||
Flags: llvm.FlagPrototyped,
|
||||
Optimized: true,
|
||||
})
|
||||
wrapper.SetSubprogram(difunc)
|
||||
builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
}
|
||||
|
||||
// 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
|
||||
params := c.emitPointerUnpack(wrapper.Param(0), paramTypes)
|
||||
params := llvmutil.EmitPointerUnpack(builder, c.mod, wrapper.Param(0), paramTypes)
|
||||
|
||||
// Get the function pointer.
|
||||
fnPtr := params[len(params)-1]
|
||||
@@ -126,13 +182,13 @@ func (c *Compiler) createGoroutineStartWrapper(fn llvm.Value) llvm.Value {
|
||||
params[len(params)-1] = llvm.Undef(c.i8ptrType)
|
||||
|
||||
// Create the call.
|
||||
c.builder.CreateCall(fnPtr, params, "")
|
||||
builder.CreateCall(fnPtr, params, "")
|
||||
}
|
||||
|
||||
// Finish the function. Every basic block must end in a terminator, and
|
||||
// because goroutines never return a value we can simply return void.
|
||||
c.builder.CreateRetVoid()
|
||||
builder.CreateRetVoid()
|
||||
|
||||
// Return a ptrtoint of the wrapper, not the function itself.
|
||||
return c.builder.CreatePtrToInt(wrapper, c.uintptrType, "")
|
||||
return builder.CreatePtrToInt(wrapper, c.uintptrType, "")
|
||||
}
|
||||
|
||||
+100
-62
@@ -13,27 +13,6 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// This is a compiler builtin, which reads the given register by name:
|
||||
//
|
||||
// func ReadRegister(name string) uintptr
|
||||
//
|
||||
// The register name must be a constant, for example "sp".
|
||||
func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value, error) {
|
||||
fnType := llvm.FunctionType(c.uintptrType, []llvm.Type{}, false)
|
||||
regname := constant.StringVal(args[0].(*ssa.Const).Value)
|
||||
var asm string
|
||||
switch name {
|
||||
case "device/arm.ReadRegister":
|
||||
asm = "mov $0, " + regname
|
||||
case "device/riscv.ReadRegister":
|
||||
asm = "mv $0, " + regname
|
||||
default:
|
||||
panic("unknown architecture")
|
||||
}
|
||||
target := llvm.InlineAsm(fnType, asm, "=r", false, false, 0)
|
||||
return c.builder.CreateCall(target, nil, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin, which emits a piece of inline assembly with no
|
||||
// operands or return values. It is useful for trivial instructions, like wfi in
|
||||
// ARM or sleep in AVR.
|
||||
@@ -41,18 +20,18 @@ func (c *Compiler) emitReadRegister(name string, args []ssa.Value) (llvm.Value,
|
||||
// func Asm(asm string)
|
||||
//
|
||||
// The provided assembly must be a constant.
|
||||
func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
|
||||
func (b *builder) createInlineAsm(args []ssa.Value) (llvm.Value, error) {
|
||||
// Magic function: insert inline assembly instead of calling it.
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{}, false)
|
||||
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)
|
||||
return c.builder.CreateCall(target, nil, ""), nil
|
||||
return b.CreateCall(target, nil, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin, which allows assembly to be called in a flexible
|
||||
// way.
|
||||
//
|
||||
// func AsmFull(asm string, regs map[string]interface{})
|
||||
// func AsmFull(asm string, regs map[string]interface{}) uintptr
|
||||
//
|
||||
// The asm parameter must be a constant string. The regs parameter must be
|
||||
// provided immediately. For example:
|
||||
@@ -63,27 +42,27 @@ func (c *Compiler) emitAsm(args []ssa.Value) (llvm.Value, error) {
|
||||
// "value": 1
|
||||
// "result": &dest,
|
||||
// })
|
||||
func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
func (b *builder) createInlineAsmFull(instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
asmString := constant.StringVal(instr.Args[0].(*ssa.Const).Value)
|
||||
registers := map[string]llvm.Value{}
|
||||
registerMap := instr.Args[1].(*ssa.MakeMap)
|
||||
for _, r := range *registerMap.Referrers() {
|
||||
switch r := r.(type) {
|
||||
case *ssa.DebugRef:
|
||||
// ignore
|
||||
case *ssa.MapUpdate:
|
||||
if r.Block() != registerMap.Block() {
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "register value map must be created in the same basic block")
|
||||
if registerMap, ok := instr.Args[1].(*ssa.MakeMap); ok {
|
||||
for _, r := range *registerMap.Referrers() {
|
||||
switch r := r.(type) {
|
||||
case *ssa.DebugRef:
|
||||
// ignore
|
||||
case *ssa.MapUpdate:
|
||||
if r.Block() != registerMap.Block() {
|
||||
return llvm.Value{}, b.makeError(instr.Pos(), "register value map must be created in the same basic block")
|
||||
}
|
||||
key := constant.StringVal(r.Key.(*ssa.Const).Value)
|
||||
registers[key] = b.getValue(r.Value.(*ssa.MakeInterface).X)
|
||||
case *ssa.Call:
|
||||
if r.Common() == instr {
|
||||
break
|
||||
}
|
||||
default:
|
||||
return llvm.Value{}, b.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
|
||||
}
|
||||
key := constant.StringVal(r.Key.(*ssa.Const).Value)
|
||||
//println("value:", r.Value.(*ssa.MakeInterface).X.String())
|
||||
registers[key] = c.getValue(frame, r.Value.(*ssa.MakeInterface).X)
|
||||
case *ssa.Call:
|
||||
if r.Common() == instr {
|
||||
break
|
||||
}
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "don't know how to handle argument to inline assembly: "+r.String())
|
||||
}
|
||||
}
|
||||
// TODO: handle dollar signs in asm string
|
||||
@@ -92,13 +71,22 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
|
||||
argTypes := []llvm.Type{}
|
||||
args := []llvm.Value{}
|
||||
constraints := []string{}
|
||||
hasOutput := false
|
||||
asmString = regexp.MustCompile("\\{\\}").ReplaceAllStringFunc(asmString, func(s string) string {
|
||||
hasOutput = true
|
||||
return "$0"
|
||||
})
|
||||
if hasOutput {
|
||||
constraints = append(constraints, "=&r")
|
||||
registerNumbers[""] = 0
|
||||
}
|
||||
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]
|
||||
if _, ok := registers[name]; !ok {
|
||||
if err == nil {
|
||||
err = c.makeError(instr.Pos(), "unknown register name: "+name)
|
||||
err = b.makeError(instr.Pos(), "unknown register name: "+name)
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -112,7 +100,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
|
||||
case llvm.PointerTypeKind:
|
||||
constraints = append(constraints, "*m")
|
||||
default:
|
||||
err = c.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
|
||||
err = b.makeError(instr.Pos(), "unknown type in inline assembly for value: "+name)
|
||||
return s
|
||||
}
|
||||
}
|
||||
@@ -121,9 +109,21 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), argTypes, false)
|
||||
var outputType llvm.Type
|
||||
if hasOutput {
|
||||
outputType = b.uintptrType
|
||||
} else {
|
||||
outputType = b.ctx.VoidType()
|
||||
}
|
||||
fnType := llvm.FunctionType(outputType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asmString, strings.Join(constraints, ","), true, false, 0)
|
||||
return c.builder.CreateCall(target, args, ""), nil
|
||||
result := b.CreateCall(target, args, "")
|
||||
if hasOutput {
|
||||
return result, nil
|
||||
} else {
|
||||
// Make sure we return something valid.
|
||||
return llvm.ConstInt(b.uintptrType, 0, false), nil
|
||||
}
|
||||
}
|
||||
|
||||
// This is a compiler builtin which emits an inline SVCall instruction. It can
|
||||
@@ -137,7 +137,7 @@ func (c *Compiler) emitAsmFull(frame *Frame, instr *ssa.CallCommon) (llvm.Value,
|
||||
//
|
||||
// The num parameter must be a constant. All other parameters may be any scalar
|
||||
// value supported by LLVM inline assembly.
|
||||
func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error) {
|
||||
func (b *builder) emitSVCall(args []ssa.Value) (llvm.Value, error) {
|
||||
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
|
||||
llvmArgs := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
@@ -150,7 +150,7 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
|
||||
} else {
|
||||
constraints += ",{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
llvmValue := b.getValue(arg)
|
||||
llvmArgs = append(llvmArgs, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
@@ -158,9 +158,47 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
|
||||
// clobbered. r0 is used as an output register so doesn't have to be
|
||||
// marked as clobbered.
|
||||
constraints += ",~{r1},~{r2},~{r3}"
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, asm, constraints, true, false, 0)
|
||||
return c.builder.CreateCall(target, llvmArgs, ""), nil
|
||||
return b.CreateCall(target, llvmArgs, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin which emits an inline SVCall instruction. It can
|
||||
// be one of:
|
||||
//
|
||||
// func SVCall0(num uintptr) uintptr
|
||||
// func SVCall1(num uintptr, a1 interface{}) uintptr
|
||||
// func SVCall2(num uintptr, a1, a2 interface{}) uintptr
|
||||
// func SVCall3(num uintptr, a1, a2, a3 interface{}) uintptr
|
||||
// func SVCall4(num uintptr, a1, a2, a3, a4 interface{}) uintptr
|
||||
//
|
||||
// The num parameter must be a constant. All other parameters may be any scalar
|
||||
// value supported by LLVM inline assembly.
|
||||
// Same as emitSVCall but for AArch64
|
||||
func (b *builder) emitSV64Call(args []ssa.Value) (llvm.Value, error) {
|
||||
num, _ := constant.Uint64Val(args[0].(*ssa.Const).Value)
|
||||
llvmArgs := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
asm := "svc #" + strconv.FormatUint(num, 10)
|
||||
constraints := "={x0}"
|
||||
for i, arg := range args[1:] {
|
||||
arg = arg.(*ssa.MakeInterface).X
|
||||
if i == 0 {
|
||||
constraints += ",0"
|
||||
} else {
|
||||
constraints += ",{x" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
llvmValue := b.getValue(arg)
|
||||
llvmArgs = append(llvmArgs, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
// Implement the ARM64 calling convention by marking x1-x7 as
|
||||
// clobbered. x0 is used as an output register so doesn't have to be
|
||||
// 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)
|
||||
return b.CreateCall(target, llvmArgs, ""), nil
|
||||
}
|
||||
|
||||
// This is a compiler builtin which emits CSR instructions. It can be one of:
|
||||
@@ -172,38 +210,38 @@ func (c *Compiler) emitSVCall(frame *Frame, args []ssa.Value) (llvm.Value, error
|
||||
//
|
||||
// The csr parameter (method receiver) must be a constant. Other parameter can
|
||||
// be any value.
|
||||
func (c *Compiler) emitCSROperation(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
|
||||
func (b *builder) emitCSROperation(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
csrConst, ok := call.Args[0].(*ssa.Const)
|
||||
if !ok {
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "CSR must be constant")
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "CSR must be constant")
|
||||
}
|
||||
csr := csrConst.Uint64()
|
||||
switch name := call.StaticCallee().Name(); name {
|
||||
case "Get":
|
||||
// Note that this instruction may have side effects, and thus must be
|
||||
// marked as such.
|
||||
fnType := llvm.FunctionType(c.uintptrType, nil, false)
|
||||
fnType := llvm.FunctionType(b.uintptrType, nil, false)
|
||||
asm := fmt.Sprintf("csrr $0, %d", csr)
|
||||
target := llvm.InlineAsm(fnType, asm, "=r", true, false, 0)
|
||||
return c.builder.CreateCall(target, nil, ""), nil
|
||||
return b.CreateCall(target, nil, ""), nil
|
||||
case "Set":
|
||||
fnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{c.uintptrType}, false)
|
||||
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)
|
||||
return c.builder.CreateCall(target, []llvm.Value{c.getValue(frame, call.Args[1])}, ""), nil
|
||||
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(c.uintptrType, []llvm.Type{c.uintptrType}, false)
|
||||
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)
|
||||
return c.builder.CreateCall(target, []llvm.Value{c.getValue(frame, call.Args[1])}, ""), nil
|
||||
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(c.uintptrType, []llvm.Type{c.uintptrType}, false)
|
||||
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)
|
||||
return c.builder.CreateCall(target, []llvm.Value{c.getValue(frame, call.Args[1])}, ""), nil
|
||||
return b.CreateCall(target, []llvm.Value{b.getValue(call.Args[1])}, ""), nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown CSR operation: "+name)
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown CSR operation: "+name)
|
||||
}
|
||||
}
|
||||
|
||||
+161
-116
@@ -11,40 +11,39 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/tinygo-org/tinygo/ir"
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// parseMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
|
||||
// createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction.
|
||||
// It tries to put the type in the interface value, but if that's not possible,
|
||||
// it will do an allocation of the right size and put that in the interface
|
||||
// value field.
|
||||
//
|
||||
// An interface value is a {typecode, value} tuple, or {i16, i8*} to be exact.
|
||||
func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value {
|
||||
itfValue := c.emitPointerPack([]llvm.Value{val})
|
||||
itfTypeCodeGlobal := c.getTypeCode(typ)
|
||||
itfMethodSetGlobal := c.getTypeMethodSet(typ)
|
||||
itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
|
||||
// An interface value is a {typecode, value} tuple named runtime._interface.
|
||||
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 := c.getLLVMRuntimeType("typeInInterface")
|
||||
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
|
||||
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 := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
|
||||
itf := llvm.Undef(c.getLLVMRuntimeType("_interface"))
|
||||
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||
itfTypeCode := b.CreatePtrToInt(itfConcreteTypeGlobal, b.uintptrType, "")
|
||||
itf := llvm.Undef(b.getLLVMRuntimeType("_interface"))
|
||||
itf = b.CreateInsertValue(itf, itfTypeCode, 0, "")
|
||||
itf = b.CreateInsertValue(itf, itfValue, 1, "")
|
||||
return itf
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||
func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value {
|
||||
globalName := "reflect/types.type:" + getTypeCodeName(typ)
|
||||
global := c.mod.NamedGlobal(globalName)
|
||||
if global.IsNil() {
|
||||
@@ -91,7 +90,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
|
||||
// makeStructTypeFields creates a new global that stores all type information
|
||||
// related to this struct type, and returns the resulting global. This global is
|
||||
// actually an array of all the fields in the structs.
|
||||
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
||||
func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value {
|
||||
// The global is an array of runtime.structField structs.
|
||||
runtimeStructField := c.getLLVMRuntimeType("structField")
|
||||
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
|
||||
@@ -228,7 +227,7 @@ func getTypeCodeName(t types.Type) string {
|
||||
|
||||
// getTypeMethodSet returns a reference (GEP) to a global method set. This
|
||||
// method set should be unreferenced after the interface lowering pass.
|
||||
func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
global := c.mod.NamedGlobal(typ.String() + "$methodset")
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
if !global.IsNil() {
|
||||
@@ -236,7 +235,7 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
|
||||
}
|
||||
|
||||
ms := c.ir.Program.MethodSets.MethodSet(typ)
|
||||
ms := c.program.MethodSets.MethodSet(typ)
|
||||
if ms.Len() == 0 {
|
||||
// no methods, so can leave that one out
|
||||
return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0))
|
||||
@@ -247,15 +246,16 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
method := ms.At(i)
|
||||
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
|
||||
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
|
||||
if f.LLVMFn.IsNil() {
|
||||
fn := c.program.MethodValue(method)
|
||||
llvmFn := c.getFunction(fn)
|
||||
if llvmFn.IsNil() {
|
||||
// compiler error, so panic
|
||||
panic("cannot find function: " + f.LinkName())
|
||||
panic("cannot find function: " + c.getFunctionInfo(fn).linkName)
|
||||
}
|
||||
fn := c.getInterfaceInvokeWrapper(f)
|
||||
wrapper := c.getInterfaceInvokeWrapper(fn, llvmFn)
|
||||
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
|
||||
signatureGlobal,
|
||||
llvm.ConstPtrToInt(fn, c.uintptrType),
|
||||
llvm.ConstPtrToInt(wrapper, c.uintptrType),
|
||||
})
|
||||
methods[i] = methodInfo
|
||||
}
|
||||
@@ -271,8 +271,13 @@ func (c *Compiler) getTypeMethodSet(typ types.Type) llvm.Value {
|
||||
// getInterfaceMethodSet returns a global variable with the method set of the
|
||||
// given named interface type. This method set is used by the interface lowering
|
||||
// pass.
|
||||
func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
|
||||
global := c.mod.NamedGlobal(typ.String() + "$interface")
|
||||
func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value {
|
||||
name := typ.String()
|
||||
if _, ok := typ.(*types.Named); !ok {
|
||||
// Anonymous interface.
|
||||
name = "reflect/types.interface:" + name
|
||||
}
|
||||
global := c.mod.NamedGlobal(name + "$interface")
|
||||
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
|
||||
if !global.IsNil() {
|
||||
// method set already exist, return it
|
||||
@@ -287,7 +292,7 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
|
||||
}
|
||||
|
||||
value := llvm.ConstArray(c.i8ptrType, methods)
|
||||
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
|
||||
global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface")
|
||||
global.SetInitializer(value)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
@@ -297,8 +302,8 @@ func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
|
||||
// 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 *Compiler) getMethodSignature(method *types.Func) llvm.Value {
|
||||
signature := ir.MethodSignature(method)
|
||||
func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value {
|
||||
signature := methodSignature(method)
|
||||
signatureGlobal := c.mod.NamedGlobal("func " + signature)
|
||||
if signatureGlobal.IsNil() {
|
||||
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
|
||||
@@ -307,18 +312,18 @@ func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
|
||||
return signatureGlobal
|
||||
}
|
||||
|
||||
// parseTypeAssert will emit the code for a typeassert, used in if statements
|
||||
// createTypeAssert will emit the code for a typeassert, used in if statements
|
||||
// and in type switches (Go SSA does not have type switches, only if/else
|
||||
// chains). Note that even though the Go SSA does not contain type switches,
|
||||
// LLVM will recognize the pattern and make it a real switch in many cases.
|
||||
//
|
||||
// Type asserts on concrete types are trivial: just compare type numbers. Type
|
||||
// asserts on interfaces are more difficult, see the comments in the function.
|
||||
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Value {
|
||||
itf := c.getValue(frame, expr.X)
|
||||
assertedType := c.getLLVMType(expr.AssertedType)
|
||||
func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
|
||||
itf := b.getValue(expr.X)
|
||||
assertedType := b.getLLVMType(expr.AssertedType)
|
||||
|
||||
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
|
||||
actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type")
|
||||
commaOk := llvm.Value{}
|
||||
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||
// Type assert on interface type.
|
||||
@@ -329,15 +334,15 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
|
||||
// the main Go compiler, where the runtime checks whether the type
|
||||
// implements each method of the interface. See:
|
||||
// https://research.swtch.com/interfaces
|
||||
methodSet := c.getInterfaceMethodSet(expr.AssertedType.(*types.Named))
|
||||
commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
|
||||
methodSet := b.getInterfaceMethodSet(expr.AssertedType)
|
||||
commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
|
||||
|
||||
} else {
|
||||
// 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 := c.getTypeCode(expr.AssertedType)
|
||||
commaOk = c.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
||||
assertedTypeCodeGlobal := b.getTypeCode(expr.AssertedType)
|
||||
commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
|
||||
}
|
||||
|
||||
// Add 2 new basic blocks (that should get optimized away): one for the
|
||||
@@ -351,15 +356,15 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
|
||||
// typeassert should return a zero value, not an incorrectly casted
|
||||
// value.
|
||||
|
||||
prevBlock := c.builder.GetInsertBlock()
|
||||
okBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.ok")
|
||||
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "typeassert.next")
|
||||
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
c.builder.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||
prevBlock := b.GetInsertBlock()
|
||||
okBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.ok")
|
||||
nextBlock := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.next")
|
||||
b.blockExits[b.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
||||
b.CreateCondBr(commaOk, okBlock, nextBlock)
|
||||
|
||||
// Retrieve the value from the interface if the type assert was
|
||||
// successful.
|
||||
c.builder.SetInsertPointAtEnd(okBlock)
|
||||
b.SetInsertPointAtEnd(okBlock)
|
||||
var valueOk llvm.Value
|
||||
if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
|
||||
// Type assert on interface type. Easy: just return the same
|
||||
@@ -368,75 +373,71 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) llvm.Valu
|
||||
} else {
|
||||
// Type assert on concrete type. Extract the underlying type from
|
||||
// the interface (but only after checking it matches).
|
||||
valuePtr := c.builder.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||
valueOk = c.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
|
||||
valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr")
|
||||
valueOk = b.emitPointerUnpack(valuePtr, []llvm.Type{assertedType})[0]
|
||||
}
|
||||
c.builder.CreateBr(nextBlock)
|
||||
b.CreateBr(nextBlock)
|
||||
|
||||
// Continue after the if statement.
|
||||
c.builder.SetInsertPointAtEnd(nextBlock)
|
||||
phi := c.builder.CreatePHI(assertedType, "typeassert.value")
|
||||
b.SetInsertPointAtEnd(nextBlock)
|
||||
phi := b.CreatePHI(assertedType, "typeassert.value")
|
||||
phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock})
|
||||
|
||||
if expr.CommaOk {
|
||||
tuple := c.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(c.ctx.Int1Type())}, false) // create empty tuple
|
||||
tuple = c.builder.CreateInsertValue(tuple, phi, 0, "") // insert value
|
||||
tuple = c.builder.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
||||
tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple
|
||||
tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value
|
||||
tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean
|
||||
return tuple
|
||||
} else {
|
||||
// This is kind of dirty as the branch above becomes mostly useless,
|
||||
// but hopefully this gets optimized away.
|
||||
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
||||
b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
|
||||
return phi
|
||||
}
|
||||
}
|
||||
|
||||
// getInvokeCall creates and returns the function pointer and parameters of an
|
||||
// interface call. It can be used in a call or defer instruction.
|
||||
func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, []llvm.Value) {
|
||||
// Call an interface method with dynamic dispatch.
|
||||
itf := c.getValue(frame, instr.Value) // interface
|
||||
|
||||
llvmFnType := c.getRawFuncType(instr.Method.Type().(*types.Signature))
|
||||
|
||||
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
|
||||
// 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,
|
||||
c.getInterfaceMethodSet(instr.Value.Type().(*types.Named)),
|
||||
c.getMethodSignature(instr.Method),
|
||||
b.getInterfaceMethodSet(instr.Value.Type()),
|
||||
b.getMethodSignature(instr.Method),
|
||||
}
|
||||
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
|
||||
fnCast := c.builder.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
|
||||
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||
fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func")
|
||||
return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast")
|
||||
}
|
||||
|
||||
// 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, c.getValue(frame, arg))
|
||||
args = append(args, b.getValue(arg))
|
||||
}
|
||||
// Add the context parameter. An interface call never takes a context but we
|
||||
// have to supply the parameter anyway.
|
||||
args = append(args, llvm.Undef(c.i8ptrType))
|
||||
args = append(args, llvm.Undef(b.i8ptrType))
|
||||
// Add the parent goroutine handle.
|
||||
args = append(args, llvm.Undef(c.i8ptrType))
|
||||
args = append(args, llvm.Undef(b.i8ptrType))
|
||||
|
||||
return fnCast, args
|
||||
}
|
||||
|
||||
// interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
|
||||
// createInterfaceInvokeWrapper. The former is called during IR construction
|
||||
// itself and the latter is called when finishing up the IR.
|
||||
type interfaceInvokeWrapper struct {
|
||||
fn *ir.Function
|
||||
wrapper llvm.Value
|
||||
receiverType llvm.Type
|
||||
}
|
||||
|
||||
// Wrap an interface method function pointer. The wrapper takes in a pointer to
|
||||
// the underlying value, dereferences it, and calls the real method. This
|
||||
// wrapper is only needed when the interface value actually doesn't fit in a
|
||||
// pointer and a pointer to the value must be created.
|
||||
func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
wrapperName := f.LinkName() + "$invoke"
|
||||
// getInterfaceInvokeWrapper returns a wrapper for the given method so it can be
|
||||
// invoked from an interface. The wrapper takes in a pointer to the underlying
|
||||
// value, dereferences or unpacks it if necessary, and calls the real method.
|
||||
// If the method to wrap has a pointer receiver, no wrapping is necessary and
|
||||
// the function is returned directly.
|
||||
func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFn llvm.Value) llvm.Value {
|
||||
wrapperName := llvmFn.Name() + "$invoke"
|
||||
wrapper := c.mod.NamedFunction(wrapperName)
|
||||
if !wrapper.IsNil() {
|
||||
// Wrapper already created. Return it directly.
|
||||
@@ -444,8 +445,11 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
}
|
||||
|
||||
// Get the expanded receiver type.
|
||||
receiverType := c.getLLVMType(f.Params[0].Type())
|
||||
expandedReceiverType := c.expandFormalParamType(receiverType)
|
||||
receiverType := c.getLLVMType(fn.Params[0].Type())
|
||||
var expandedReceiverType []llvm.Type
|
||||
for _, info := range expandFormalParamType(receiverType, "", nil) {
|
||||
expandedReceiverType = append(expandedReceiverType, info.llvmType)
|
||||
}
|
||||
|
||||
// Does this method even need any wrapping?
|
||||
if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind {
|
||||
@@ -453,52 +457,93 @@ func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) llvm.Value {
|
||||
// Casting a function signature to a different signature and calling it
|
||||
// with a receiver pointer bitcasted to *i8 (as done in calls on an
|
||||
// interface) is hopefully a safe (defined) operation.
|
||||
return f.LLVMFn
|
||||
return llvmFn
|
||||
}
|
||||
|
||||
// create wrapper function
|
||||
fnType := f.LLVMFn.Type().ElementType()
|
||||
fnType := llvmFn.Type().ElementType()
|
||||
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
|
||||
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
|
||||
wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
|
||||
if f.LLVMFn.LastParam().Name() == "parentHandle" {
|
||||
wrapper.LastParam().SetName("parentHandle")
|
||||
}
|
||||
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
|
||||
fn: f,
|
||||
wrapper: wrapper,
|
||||
receiverType: receiverType,
|
||||
})
|
||||
return wrapper
|
||||
}
|
||||
wrapper.LastParam().SetName("parentHandle")
|
||||
|
||||
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
|
||||
// see that function for details.
|
||||
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) {
|
||||
wrapper := state.wrapper
|
||||
fn := state.fn
|
||||
receiverType := state.receiverType
|
||||
wrapper.SetLinkage(llvm.InternalLinkage)
|
||||
wrapper.SetUnnamedAddr(true)
|
||||
|
||||
// Create a new builder just to create this wrapper.
|
||||
b := builder{
|
||||
compilerContext: c,
|
||||
Builder: c.ctx.NewBuilder(),
|
||||
}
|
||||
defer b.Builder.Dispose()
|
||||
|
||||
// add debug info if needed
|
||||
if c.Debug() {
|
||||
pos := c.ir.Program.Fset.Position(fn.Pos())
|
||||
if c.Debug {
|
||||
pos := c.program.Fset.Position(fn.Pos())
|
||||
difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
|
||||
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
|
||||
}
|
||||
|
||||
// set up IR builder
|
||||
block := c.ctx.AddBasicBlock(wrapper, "entry")
|
||||
c.builder.SetInsertPointAtEnd(block)
|
||||
block := b.ctx.AddBasicBlock(wrapper, "entry")
|
||||
b.SetInsertPointAtEnd(block)
|
||||
|
||||
receiverValue := c.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||
if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||
c.builder.CreateCall(fn.LLVMFn, params, "")
|
||||
c.builder.CreateRetVoid()
|
||||
receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
|
||||
params := append(b.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
|
||||
if llvmFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
|
||||
b.CreateCall(llvmFn, params, "")
|
||||
b.CreateRetVoid()
|
||||
} else {
|
||||
ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
|
||||
c.builder.CreateRet(ret)
|
||||
ret := b.CreateCall(llvmFn, params, "ret")
|
||||
b.CreateRet(ret)
|
||||
}
|
||||
|
||||
return wrapper
|
||||
}
|
||||
|
||||
// methodSignature creates a readable version of a method signature (including
|
||||
// the function name, excluding the receiver name). This string is used
|
||||
// internally to match interfaces and to call the correct method on an
|
||||
// interface. Examples:
|
||||
//
|
||||
// String() string
|
||||
// Read([]byte) (int, error)
|
||||
func methodSignature(method *types.Func) string {
|
||||
return method.Name() + signature(method.Type().(*types.Signature))
|
||||
}
|
||||
|
||||
// Make a readable version of a function (pointer) signature.
|
||||
// Examples:
|
||||
//
|
||||
// () string
|
||||
// (string, int) (int, error)
|
||||
func signature(sig *types.Signature) string {
|
||||
s := ""
|
||||
if sig.Params().Len() == 0 {
|
||||
s += "()"
|
||||
} else {
|
||||
s += "("
|
||||
for i := 0; i < sig.Params().Len(); i++ {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += sig.Params().At(i).Type().String()
|
||||
}
|
||||
s += ")"
|
||||
}
|
||||
if sig.Results().Len() == 0 {
|
||||
// keep as-is
|
||||
} else if sig.Results().Len() == 1 {
|
||||
s += " " + sig.Results().At(0).Type().String()
|
||||
} else {
|
||||
s += " ("
|
||||
for i := 0; i < sig.Results().Len(); i++ {
|
||||
if i > 0 {
|
||||
s += ", "
|
||||
}
|
||||
s += sig.Results().At(i).Type().String()
|
||||
}
|
||||
s += ")"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
+25
-25
@@ -8,62 +8,62 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
|
||||
// createInterruptGlobal creates a new runtime/interrupt.Interrupt struct that
|
||||
// will be lowered to a real interrupt during interrupt lowering.
|
||||
//
|
||||
// This two-stage approach allows unused interrupts to be optimized away if
|
||||
// necessary.
|
||||
func (c *Compiler) emitInterruptGlobal(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
func (b *builder) createInterruptGlobal(instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
// Get the interrupt number, which must be a compile-time constant.
|
||||
id, ok := instr.Args[0].(*ssa.Const)
|
||||
if !ok {
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt ID is not a constant")
|
||||
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt ID is not a constant")
|
||||
}
|
||||
|
||||
// Get the func value, which also must be a compile time constant.
|
||||
// Note that bound functions are allowed if the function has a pointer
|
||||
// receiver and is a global. This is rather strict but still allows for
|
||||
// idiomatic Go code.
|
||||
funcValue := c.getValue(frame, instr.Args[1])
|
||||
funcValue := b.getValue(instr.Args[1])
|
||||
if funcValue.IsAConstant().IsNil() {
|
||||
// Try to determine the cause of the non-constantness for a nice error
|
||||
// message.
|
||||
switch instr.Args[1].(type) {
|
||||
case *ssa.MakeClosure:
|
||||
// This may also be a bound method.
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "closures are not supported in interrupt.New")
|
||||
return llvm.Value{}, b.makeError(instr.Pos(), "closures are not supported in interrupt.New")
|
||||
}
|
||||
// Fall back to a generic error.
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt function must be constant")
|
||||
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt function must be constant")
|
||||
}
|
||||
|
||||
// Create a new global of type runtime/interrupt.handle. Globals of this
|
||||
// type are lowered in the interrupt lowering pass.
|
||||
globalType := c.ir.Program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
||||
globalLLVMType := c.getLLVMType(globalType)
|
||||
globalType := b.program.ImportedPackage("runtime/interrupt").Type("handle").Type()
|
||||
globalLLVMType := b.getLLVMType(globalType)
|
||||
globalName := "runtime/interrupt.$interrupt" + strconv.FormatInt(id.Int64(), 10)
|
||||
if global := c.mod.NamedGlobal(globalName); !global.IsNil() {
|
||||
return llvm.Value{}, c.makeError(instr.Pos(), "interrupt redeclared in this program")
|
||||
if global := b.mod.NamedGlobal(globalName); !global.IsNil() {
|
||||
return llvm.Value{}, b.makeError(instr.Pos(), "interrupt redeclared in this program")
|
||||
}
|
||||
global := llvm.AddGlobal(c.mod, globalLLVMType, globalName)
|
||||
global := llvm.AddGlobal(b.mod, globalLLVMType, globalName)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
initializer := llvm.ConstNull(globalLLVMType)
|
||||
initializer = llvm.ConstInsertValue(initializer, funcValue, []uint32{0})
|
||||
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(c.intType, uint64(id.Int64()), true), []uint32{1, 0})
|
||||
initializer = llvm.ConstInsertValue(initializer, llvm.ConstInt(b.intType, uint64(id.Int64()), true), []uint32{1, 0})
|
||||
global.SetInitializer(initializer)
|
||||
|
||||
// Add debug info to the interrupt global.
|
||||
if c.Debug() {
|
||||
pos := c.ir.Program.Fset.Position(instr.Pos())
|
||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
||||
if b.Debug {
|
||||
pos := b.program.Fset.Position(instr.Pos())
|
||||
diglobal := b.dibuilder.CreateGlobalVariableExpression(b.getDIFile(pos.Filename), llvm.DIGlobalVariableExpression{
|
||||
Name: "interrupt" + strconv.FormatInt(id.Int64(), 10),
|
||||
LinkageName: globalName,
|
||||
File: c.getDIFile(pos.Filename),
|
||||
File: b.getDIFile(pos.Filename),
|
||||
Line: pos.Line,
|
||||
Type: c.getDIType(globalType),
|
||||
Expr: c.dibuilder.CreateExpression(nil),
|
||||
Type: b.getDIType(globalType),
|
||||
Expr: b.dibuilder.CreateExpression(nil),
|
||||
LocalToUnit: false,
|
||||
})
|
||||
global.AddMetadata(0, diglobal)
|
||||
@@ -71,21 +71,21 @@ func (c *Compiler) emitInterruptGlobal(frame *Frame, instr *ssa.CallCommon) (llv
|
||||
|
||||
// Create the runtime/interrupt.Interrupt type. It is a struct with a single
|
||||
// member of type int.
|
||||
num := llvm.ConstPtrToInt(global, c.intType)
|
||||
interrupt := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
|
||||
num := llvm.ConstPtrToInt(global, b.intType)
|
||||
interrupt := llvm.ConstNamedStruct(b.mod.GetTypeByName("runtime/interrupt.Interrupt"), []llvm.Value{num})
|
||||
|
||||
// Add dummy "use" call for AVR, because interrupts may be used even though
|
||||
// they are never referenced again. This is unlike Cortex-M or the RISC-V
|
||||
// PLIC where each interrupt must be enabled using the interrupt number, and
|
||||
// thus keeps the Interrupt object alive.
|
||||
// This call is removed during interrupt lowering.
|
||||
if strings.HasPrefix(c.Triple(), "avr") {
|
||||
useFn := c.mod.NamedFunction("runtime/interrupt.use")
|
||||
if strings.HasPrefix(b.Triple, "avr") {
|
||||
useFn := b.mod.NamedFunction("runtime/interrupt.use")
|
||||
if useFn.IsNil() {
|
||||
useFnType := llvm.FunctionType(c.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
|
||||
useFn = llvm.AddFunction(c.mod, "runtime/interrupt.use", useFnType)
|
||||
useFnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{interrupt.Type()}, false)
|
||||
useFn = llvm.AddFunction(b.mod, "runtime/interrupt.use", useFnType)
|
||||
}
|
||||
c.builder.CreateCall(useFn, []llvm.Value{interrupt}, "")
|
||||
b.CreateCall(useFn, []llvm.Value{interrupt}, "")
|
||||
}
|
||||
|
||||
return interrupt, nil
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package compiler
|
||||
|
||||
// This file contains helper functions to create calls to LLVM intrinsics.
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/tools/go/ssa"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// createMemoryCopyCall creates a call to a builtin LLVM memcpy or memmove
|
||||
// function, declaring this function if needed. These calls are treated
|
||||
// specially by optimization passes possibly resulting in better generated code,
|
||||
// and will otherwise be lowered to regular libc memcpy/memmove calls.
|
||||
func (b *builder) createMemoryCopyCall(fn *ssa.Function, args []ssa.Value) (llvm.Value, error) {
|
||||
fnName := "llvm." + fn.Name() + ".p0i8.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
|
||||
llvmFn := b.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.i8ptrType, b.uintptrType, b.ctx.Int1Type()}, false)
|
||||
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
|
||||
}
|
||||
var params []llvm.Value
|
||||
for _, param := range args {
|
||||
params = append(params, b.getValue(param))
|
||||
}
|
||||
params = append(params, llvm.ConstInt(b.ctx.Int1Type(), 0, false))
|
||||
b.CreateCall(llvmFn, params, "")
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
|
||||
// createMemoryZeroCall creates calls to llvm.memset.* to zero a block of
|
||||
// memory, declaring the function if needed. These calls will be lowered to
|
||||
// regular libc memset calls if they aren't optimized out in a different way.
|
||||
func (b *builder) createMemoryZeroCall(args []ssa.Value) (llvm.Value, error) {
|
||||
fnName := "llvm.memset.p0i8.i" + strconv.Itoa(b.uintptrType.IntTypeWidth())
|
||||
llvmFn := b.mod.NamedFunction(fnName)
|
||||
if llvmFn.IsNil() {
|
||||
fnType := llvm.FunctionType(b.ctx.VoidType(), []llvm.Type{b.i8ptrType, b.ctx.Int8Type(), b.uintptrType, b.ctx.Int1Type()}, false)
|
||||
llvmFn = llvm.AddFunction(b.mod, fnName, fnType)
|
||||
}
|
||||
params := []llvm.Value{
|
||||
b.getValue(args[0]),
|
||||
llvm.ConstInt(b.ctx.Int8Type(), 0, false),
|
||||
b.getValue(args[1]),
|
||||
llvm.ConstInt(b.ctx.Int1Type(), 0, false),
|
||||
}
|
||||
b.CreateCall(llvmFn, params, "")
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
package compiler
|
||||
|
||||
// This file implements a set of sanity checks for the IR that is generated.
|
||||
// It can catch some mistakes that LLVM's verifier cannot.
|
||||
// Package ircheck implements a checker for LLVM IR, that goes a bit further
|
||||
// than the regular LLVM IR verifier. Note that it checks different things, so
|
||||
// this is not a replacement for the LLVM verifier but does catch things that
|
||||
// the LLVM verifier doesn't catch.
|
||||
package ircheck
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -10,7 +11,11 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
type checker struct {
|
||||
ctx llvm.Context
|
||||
}
|
||||
|
||||
func (c *checker) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
// prevent infinite recursion for self-referential types
|
||||
if _, ok := checked[t]; ok {
|
||||
return nil
|
||||
@@ -81,7 +86,7 @@ func (c *Compiler) checkType(t llvm.Type, checked map[llvm.Type]struct{}, specia
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
func (c *checker) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
// check type
|
||||
if err := c.checkType(v.Type(), types, specials); err != nil {
|
||||
return fmt.Errorf("failed to verify type of value: %s", err.Error())
|
||||
@@ -95,12 +100,30 @@ func (c *Compiler) checkValue(v llvm.Value, types map[llvm.Type]struct{}, specia
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
func (c *checker) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) error {
|
||||
// check value properties
|
||||
if err := c.checkValue(inst, types, specials); err != nil {
|
||||
return errorAt(inst, err.Error())
|
||||
}
|
||||
|
||||
// The alloca instruction can be present in every basic block. However,
|
||||
// allocas in basic blocks other than the entry basic block have a number of
|
||||
// problems:
|
||||
// * They are hard to optimize, leading to potential missed optimizations.
|
||||
// * They may cause stack overflows in loops that would otherwise be
|
||||
// innocent.
|
||||
// * They cause extra code to be generated, because it requires the use of
|
||||
// a frame pointer.
|
||||
// * Perhaps most importantly, the coroutine lowering pass of LLVM (as of
|
||||
// LLVM 9) cannot deal with these allocas:
|
||||
// https://llvm.org/docs/Coroutines.html
|
||||
// Therefore, alloca instructions should be limited to the entry block.
|
||||
if !inst.IsAAllocaInst().IsNil() {
|
||||
if inst.InstructionParent() != inst.InstructionParent().Parent().EntryBasicBlock() {
|
||||
return errorAt(inst, "internal error: non-static alloca")
|
||||
}
|
||||
}
|
||||
|
||||
// check operands
|
||||
for i := 0; i < inst.OperandsCount(); i++ {
|
||||
if err := c.checkValue(inst.Operand(i), types, specials); err != nil {
|
||||
@@ -111,7 +134,7 @@ func (c *Compiler) checkInstruction(inst llvm.Value, types map[llvm.Type]struct{
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
||||
func (c *checker) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
||||
// check basic block value and type
|
||||
var errs []error
|
||||
if err := c.checkValue(bb.AsValue(), types, specials); err != nil {
|
||||
@@ -128,7 +151,7 @@ func (c *Compiler) checkBasicBlock(bb llvm.BasicBlock, types map[llvm.Type]struc
|
||||
return errs
|
||||
}
|
||||
|
||||
func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
||||
func (c *checker) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, specials map[llvm.TypeKind]llvm.Type) []error {
|
||||
// check function value and type
|
||||
var errs []error
|
||||
if err := c.checkValue(fn, types, specials); err != nil {
|
||||
@@ -143,26 +166,25 @@ func (c *Compiler) checkFunction(fn llvm.Value, types map[llvm.Type]struct{}, sp
|
||||
return errs
|
||||
}
|
||||
|
||||
func (c *Compiler) checkModule() []error {
|
||||
// Module checks the given module and returns a slice of error, if there are
|
||||
// any.
|
||||
func Module(mod llvm.Module) []error {
|
||||
// check for any context mismatches
|
||||
var errs []error
|
||||
switch {
|
||||
case c.mod.Context() == c.ctx:
|
||||
// this is correct
|
||||
case c.mod.Context() == llvm.GlobalContext():
|
||||
c := checker{
|
||||
ctx: mod.Context(),
|
||||
}
|
||||
if c.ctx == llvm.GlobalContext() {
|
||||
// somewhere we accidentally used the global context instead of a real context
|
||||
errs = append(errs, errors.New("module uses global context"))
|
||||
default:
|
||||
// we used some other context by accident
|
||||
errs = append(errs, fmt.Errorf("module uses context %v instead of the main context %v", c.mod.Context(), c.ctx))
|
||||
}
|
||||
|
||||
types := map[llvm.Type]struct{}{}
|
||||
specials := map[llvm.TypeKind]llvm.Type{}
|
||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
errs = append(errs, c.checkFunction(fn, types, specials)...)
|
||||
}
|
||||
for g := c.mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
|
||||
for g := mod.FirstGlobal(); !g.IsNil(); g = llvm.NextGlobal(g) {
|
||||
if err := c.checkValue(g, types, specials); err != nil {
|
||||
errs = append(errs, fmt.Errorf("failed to verify global %s of module: %s", g.Name(), err.Error()))
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ircheck
|
||||
|
||||
import (
|
||||
"go/scanner"
|
||||
"go/token"
|
||||
"path/filepath"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
if !val.IsAInstruction().IsNil() {
|
||||
loc := val.InstructionDebugLoc()
|
||||
if loc.IsNil() {
|
||||
return token.Position{}
|
||||
}
|
||||
file := loc.LocationScope().ScopeFile()
|
||||
return token.Position{
|
||||
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||
Line: int(loc.LocationLine()),
|
||||
Column: int(loc.LocationColumn()),
|
||||
}
|
||||
} else if !val.IsAFunction().IsNil() {
|
||||
loc := val.Subprogram()
|
||||
if loc.IsNil() {
|
||||
return token.Position{}
|
||||
}
|
||||
file := loc.ScopeFile()
|
||||
return token.Position{
|
||||
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||
Line: int(loc.SubprogramLine()),
|
||||
}
|
||||
} else {
|
||||
return token.Position{}
|
||||
}
|
||||
}
|
||||
+10
-10
@@ -24,39 +24,39 @@ func getUses(value llvm.Value) []llvm.Value {
|
||||
}
|
||||
|
||||
// createTemporaryAlloca creates a new alloca in the entry block and adds
|
||||
// lifetime start infromation in the IR signalling that the alloca won't be used
|
||||
// lifetime start information in the IR signalling that the alloca won't be used
|
||||
// before this point.
|
||||
//
|
||||
// This is useful for creating temporary allocas for intrinsics. Don't forget to
|
||||
// end the lifetime using emitLifetimeEnd after you're done with it.
|
||||
func (c *Compiler) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||
return llvmutil.CreateTemporaryAlloca(c.builder, c.mod, t, name)
|
||||
func (b *builder) createTemporaryAlloca(t llvm.Type, name string) (alloca, bitcast, size llvm.Value) {
|
||||
return llvmutil.CreateTemporaryAlloca(b.Builder, b.mod, t, name)
|
||||
}
|
||||
|
||||
// emitLifetimeEnd signals the end of an (alloca) lifetime by calling the
|
||||
// llvm.lifetime.end intrinsic. It is commonly used together with
|
||||
// createTemporaryAlloca.
|
||||
func (c *Compiler) emitLifetimeEnd(ptr, size llvm.Value) {
|
||||
llvmutil.EmitLifetimeEnd(c.builder, c.mod, ptr, size)
|
||||
func (b *builder) emitLifetimeEnd(ptr, size llvm.Value) {
|
||||
llvmutil.EmitLifetimeEnd(b.Builder, b.mod, ptr, size)
|
||||
}
|
||||
|
||||
// emitPointerPack packs the list of values into a single pointer value using
|
||||
// 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 (c *Compiler) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
return llvmutil.EmitPointerPack(c.builder, c.mod, c.Config, values)
|
||||
func (b *builder) emitPointerPack(values []llvm.Value) llvm.Value {
|
||||
return llvmutil.EmitPointerPack(b.Builder, b.mod, b.NeedsStackObjects, values)
|
||||
}
|
||||
|
||||
// emitPointerUnpack extracts a list of values packed using emitPointerPack.
|
||||
func (c *Compiler) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
||||
return llvmutil.EmitPointerUnpack(c.builder, c.mod, ptr, valueTypes)
|
||||
func (b *builder) emitPointerUnpack(ptr llvm.Value, valueTypes []llvm.Type) []llvm.Value {
|
||||
return llvmutil.EmitPointerUnpack(b.Builder, b.mod, ptr, valueTypes)
|
||||
}
|
||||
|
||||
// makeGlobalArray creates a new LLVM global with the given name and integers as
|
||||
// contents, and returns the global.
|
||||
// Note that it is left with the default linkage etc., you should set
|
||||
// linkage/constant/etc properties yourself.
|
||||
func (c *Compiler) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
|
||||
func (c *compilerContext) makeGlobalArray(buf []byte, name string, elementType llvm.Type) llvm.Value {
|
||||
globalType := llvm.ArrayType(elementType, len(buf))
|
||||
global := llvm.AddGlobal(c.mod, globalType, name)
|
||||
value := llvm.Undef(globalType)
|
||||
|
||||
@@ -5,14 +5,14 @@ package llvmutil
|
||||
// itself if possible and legal.
|
||||
|
||||
import (
|
||||
"github.com/tinygo-org/tinygo/compileopts"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// EmitPointerPack packs the list of values into a single pointer value using
|
||||
// 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 EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.Config, values []llvm.Value) llvm.Value {
|
||||
// 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 {
|
||||
ctx := mod.Context()
|
||||
targetData := llvm.NewTargetData(mod.DataLayout())
|
||||
i8ptrType := llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||
@@ -25,7 +25,6 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
|
||||
packedType := ctx.StructType(valueTypes, false)
|
||||
|
||||
// Allocate memory for the packed data.
|
||||
var packedAlloc, packedHeapAlloc llvm.Value
|
||||
size := targetData.TypeAllocSize(packedType)
|
||||
if size == 0 {
|
||||
return llvm.ConstPointerNull(i8ptrType)
|
||||
@@ -38,19 +37,70 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
|
||||
// Try to keep this cast in SSA form.
|
||||
return builder.CreateIntToPtr(values[0], i8ptrType, "pack.int")
|
||||
}
|
||||
|
||||
// Because packedType is a struct and we have to cast it to a *i8, store
|
||||
// it in an alloca first for bitcasting (store+bitcast+load).
|
||||
packedAlloc, _, _ = CreateTemporaryAlloca(builder, mod, packedType, "")
|
||||
// it in a *i8 alloca first and load the *i8 value from there. This is
|
||||
// effectively a bitcast.
|
||||
packedAlloc, _, _ := CreateTemporaryAlloca(builder, mod, i8ptrType, "")
|
||||
|
||||
if size < targetData.TypeAllocSize(i8ptrType) {
|
||||
// The alloca is bigger than the value that will be stored in it.
|
||||
// To avoid having some bits undefined, zero the alloca first.
|
||||
// Hopefully this will get optimized away.
|
||||
builder.CreateStore(llvm.ConstNull(i8ptrType), packedAlloc)
|
||||
}
|
||||
|
||||
// Store all values in the alloca.
|
||||
packedAllocCast := builder.CreateBitCast(packedAlloc, llvm.PointerType(packedType, 0), "")
|
||||
for i, value := range values {
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := builder.CreateInBoundsGEP(packedAllocCast, indices, "")
|
||||
builder.CreateStore(value, gep)
|
||||
}
|
||||
|
||||
// Load value (the *i8) from the alloca.
|
||||
result := builder.CreateLoad(packedAlloc, "")
|
||||
|
||||
// End the lifetime of the alloca, to help the optimizer.
|
||||
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
|
||||
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
|
||||
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
|
||||
|
||||
return result
|
||||
} else {
|
||||
// Check if the values are all constants.
|
||||
constant := true
|
||||
for _, v := range values {
|
||||
if !v.IsConstant() {
|
||||
constant = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
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.SetInitializer(ctx.ConstStruct(values, false))
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
global.SetLinkage(llvm.PrivateLinkage)
|
||||
return llvm.ConstBitCast(global, i8ptrType)
|
||||
}
|
||||
|
||||
// Packed data is bigger than a pointer, so allocate it on the heap.
|
||||
sizeValue := llvm.ConstInt(uintptrType, size, false)
|
||||
alloc := mod.NamedFunction("runtime.alloc")
|
||||
packedHeapAlloc = builder.CreateCall(alloc, []llvm.Value{
|
||||
packedHeapAlloc := builder.CreateCall(alloc, []llvm.Value{
|
||||
sizeValue,
|
||||
llvm.Undef(i8ptrType), // unused context parameter
|
||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||
}, "")
|
||||
if config.NeedsStackObjects() {
|
||||
if needsStackObjects {
|
||||
trackPointer := mod.NamedFunction("runtime.trackPointer")
|
||||
builder.CreateCall(trackPointer, []llvm.Value{
|
||||
packedHeapAlloc,
|
||||
@@ -58,28 +108,19 @@ func EmitPointerPack(builder llvm.Builder, mod llvm.Module, config *compileopts.
|
||||
llvm.ConstPointerNull(i8ptrType), // coroutine handle
|
||||
}, "")
|
||||
}
|
||||
packedAlloc = builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||
}
|
||||
// Store all values in the alloca or heap pointer.
|
||||
for i, value := range values {
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||
builder.CreateStore(value, gep)
|
||||
}
|
||||
packedAlloc := builder.CreateBitCast(packedHeapAlloc, llvm.PointerType(packedType, 0), "")
|
||||
|
||||
if packedHeapAlloc.IsNil() {
|
||||
// Load value (as *i8) from the alloca.
|
||||
packedAlloc = builder.CreateBitCast(packedAlloc, llvm.PointerType(i8ptrType, 0), "")
|
||||
result := builder.CreateLoad(packedAlloc, "")
|
||||
packedPtr := builder.CreateBitCast(packedAlloc, i8ptrType, "")
|
||||
packedSize := llvm.ConstInt(ctx.Int64Type(), targetData.TypeAllocSize(packedAlloc.Type()), false)
|
||||
EmitLifetimeEnd(builder, mod, packedPtr, packedSize)
|
||||
return result
|
||||
} else {
|
||||
// Get the original heap allocation pointer, which already is an *i8.
|
||||
// Store all values in the heap pointer.
|
||||
for i, value := range values {
|
||||
indices := []llvm.Value{
|
||||
llvm.ConstInt(ctx.Int32Type(), 0, false),
|
||||
llvm.ConstInt(ctx.Int32Type(), uint64(i), false),
|
||||
}
|
||||
gep := builder.CreateInBoundsGEP(packedAlloc, indices, "")
|
||||
builder.CreateStore(value, gep)
|
||||
}
|
||||
|
||||
// Return the original heap allocation pointer, which already is an *i8.
|
||||
return packedHeapAlloc
|
||||
}
|
||||
}
|
||||
|
||||
+66
-51
@@ -10,133 +10,148 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitMakeMap creates a new map object (runtime.hashmap) by allocating and
|
||||
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
|
||||
// initializing an appropriately sized object.
|
||||
func (c *Compiler) emitMakeMap(frame *Frame, expr *ssa.MakeMap) (llvm.Value, error) {
|
||||
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
|
||||
mapType := expr.Type().Underlying().(*types.Map)
|
||||
keyType := mapType.Key().Underlying()
|
||||
llvmValueType := c.getLLVMType(mapType.Elem().Underlying())
|
||||
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
|
||||
var llvmKeyType llvm.Type
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// String keys.
|
||||
llvmKeyType = c.getLLVMType(keyType)
|
||||
llvmKeyType = b.getLLVMType(keyType)
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// Trivially comparable keys.
|
||||
llvmKeyType = c.getLLVMType(keyType)
|
||||
llvmKeyType = b.getLLVMType(keyType)
|
||||
} else {
|
||||
// All other keys. Implemented as map[interface{}]valueType for ease of
|
||||
// implementation.
|
||||
llvmKeyType = c.getLLVMRuntimeType("_interface")
|
||||
llvmKeyType = b.getLLVMRuntimeType("_interface")
|
||||
}
|
||||
keySize := c.targetData.TypeAllocSize(llvmKeyType)
|
||||
valueSize := c.targetData.TypeAllocSize(llvmValueType)
|
||||
llvmKeySize := llvm.ConstInt(c.ctx.Int8Type(), keySize, false)
|
||||
llvmValueSize := llvm.ConstInt(c.ctx.Int8Type(), valueSize, false)
|
||||
sizeHint := llvm.ConstInt(c.uintptrType, 8, false)
|
||||
keySize := b.targetData.TypeAllocSize(llvmKeyType)
|
||||
valueSize := b.targetData.TypeAllocSize(llvmValueType)
|
||||
llvmKeySize := llvm.ConstInt(b.ctx.Int8Type(), keySize, false)
|
||||
llvmValueSize := llvm.ConstInt(b.ctx.Int8Type(), valueSize, false)
|
||||
sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
|
||||
if expr.Reserve != nil {
|
||||
sizeHint = c.getValue(frame, expr.Reserve)
|
||||
sizeHint = b.getValue(expr.Reserve)
|
||||
var err error
|
||||
sizeHint, err = c.parseConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos())
|
||||
sizeHint, err = b.createConvert(expr.Reserve.Type(), types.Typ[types.Uintptr], sizeHint, expr.Pos())
|
||||
if err != nil {
|
||||
return llvm.Value{}, err
|
||||
}
|
||||
}
|
||||
hashmap := c.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "")
|
||||
hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "")
|
||||
return hashmap, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) emitMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
||||
llvmValueType := c.getLLVMType(valueType)
|
||||
// createMapLookup returns the value in a map. It calls a runtime function
|
||||
// depending on the map key type to load the map value and its comma-ok value.
|
||||
func (b *builder) createMapLookup(keyType, valueType types.Type, m, key llvm.Value, commaOk bool, pos token.Pos) (llvm.Value, error) {
|
||||
llvmValueType := b.getLLVMType(valueType)
|
||||
|
||||
// Allocate the memory for the resulting type. Do not zero this memory: it
|
||||
// will be zeroed by the hashmap get implementation if the key is not
|
||||
// present in the map.
|
||||
mapValueAlloca, mapValuePtr, mapValueSize := c.createTemporaryAlloca(llvmValueType, "hashmap.value")
|
||||
mapValueAlloca, mapValuePtr, mapValueAllocaSize := b.createTemporaryAlloca(llvmValueType, "hashmap.value")
|
||||
|
||||
// We need the map size (with type uintptr) to pass to the hashmap*Get
|
||||
// functions. This is necessary because those *Get functions are valid on
|
||||
// nil maps, and they'll need to zero the value pointer by that number of
|
||||
// bytes.
|
||||
mapValueSize := mapValueAllocaSize
|
||||
if mapValueSize.Type().IntTypeWidth() > b.uintptrType.IntTypeWidth() {
|
||||
mapValueSize = llvm.ConstTrunc(mapValueSize, b.uintptrType)
|
||||
}
|
||||
|
||||
// Do the lookup. How it is done depends on the key type.
|
||||
var commaOkValue llvm.Value
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, key, mapValuePtr}
|
||||
commaOkValue = c.createRuntimeCall("hashmapStringGet", params, "")
|
||||
params := []llvm.Value{m, key, mapValuePtr, mapValueSize}
|
||||
commaOkValue = b.createRuntimeCall("hashmapStringGet", params, "")
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// key can be compared with runtime.memequal
|
||||
// Store the key in an alloca, in the entry block to avoid dynamic stack
|
||||
// growth.
|
||||
mapKeyAlloca, mapKeyPtr, mapKeySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, mapKeyAlloca)
|
||||
mapKeyAlloca, mapKeyPtr, mapKeySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
b.CreateStore(key, mapKeyAlloca)
|
||||
// Fetch the value from the hashmap.
|
||||
params := []llvm.Value{m, mapKeyPtr, mapValuePtr}
|
||||
commaOkValue = c.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||
c.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
||||
params := []llvm.Value{m, mapKeyPtr, mapValuePtr, mapValueSize}
|
||||
commaOkValue = b.createRuntimeCall("hashmapBinaryGet", params, "")
|
||||
b.emitLifetimeEnd(mapKeyPtr, mapKeySize)
|
||||
} else {
|
||||
// Not trivially comparable using memcmp. Make it an interface instead.
|
||||
itfKey := key
|
||||
if _, ok := keyType.(*types.Interface); !ok {
|
||||
// Not already an interface, so convert it to an interface now.
|
||||
itfKey = c.parseMakeInterface(key, keyType, pos)
|
||||
itfKey = b.createMakeInterface(key, keyType, pos)
|
||||
}
|
||||
params := []llvm.Value{m, itfKey, mapValuePtr}
|
||||
commaOkValue = c.createRuntimeCall("hashmapInterfaceGet", params, "")
|
||||
params := []llvm.Value{m, itfKey, mapValuePtr, mapValueSize}
|
||||
commaOkValue = b.createRuntimeCall("hashmapInterfaceGet", params, "")
|
||||
}
|
||||
|
||||
// Load the resulting value from the hashmap. The value is set to the zero
|
||||
// value if the key doesn't exist in the hashmap.
|
||||
mapValue := c.builder.CreateLoad(mapValueAlloca, "")
|
||||
c.emitLifetimeEnd(mapValuePtr, mapValueSize)
|
||||
mapValue := b.CreateLoad(mapValueAlloca, "")
|
||||
b.emitLifetimeEnd(mapValuePtr, mapValueAllocaSize)
|
||||
|
||||
if commaOk {
|
||||
tuple := llvm.Undef(c.ctx.StructType([]llvm.Type{llvmValueType, c.ctx.Int1Type()}, false))
|
||||
tuple = c.builder.CreateInsertValue(tuple, mapValue, 0, "")
|
||||
tuple = c.builder.CreateInsertValue(tuple, commaOkValue, 1, "")
|
||||
tuple := llvm.Undef(b.ctx.StructType([]llvm.Type{llvmValueType, b.ctx.Int1Type()}, false))
|
||||
tuple = b.CreateInsertValue(tuple, mapValue, 0, "")
|
||||
tuple = b.CreateInsertValue(tuple, commaOkValue, 1, "")
|
||||
return tuple, nil
|
||||
} else {
|
||||
return mapValue, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Compiler) emitMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
||||
valueAlloca, valuePtr, valueSize := c.createTemporaryAlloca(value.Type(), "hashmap.value")
|
||||
c.builder.CreateStore(value, valueAlloca)
|
||||
// createMapUpdate updates a map key to a given value, by creating an
|
||||
// appropriate runtime call.
|
||||
func (b *builder) createMapUpdate(keyType types.Type, m, key, value llvm.Value, pos token.Pos) {
|
||||
valueAlloca, valuePtr, valueSize := b.createTemporaryAlloca(value.Type(), "hashmap.value")
|
||||
b.CreateStore(value, valueAlloca)
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, key, valuePtr}
|
||||
c.createRuntimeCall("hashmapStringSet", params, "")
|
||||
b.createRuntimeCall("hashmapStringSet", params, "")
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
// key can be compared with runtime.memequal
|
||||
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
b.CreateStore(key, keyAlloca)
|
||||
params := []llvm.Value{m, keyPtr, valuePtr}
|
||||
c.createRuntimeCall("hashmapBinarySet", params, "")
|
||||
c.emitLifetimeEnd(keyPtr, keySize)
|
||||
b.createRuntimeCall("hashmapBinarySet", params, "")
|
||||
b.emitLifetimeEnd(keyPtr, keySize)
|
||||
} else {
|
||||
// Key is not trivially comparable, so compare it as an interface instead.
|
||||
itfKey := key
|
||||
if _, ok := keyType.(*types.Interface); !ok {
|
||||
// Not already an interface, so convert it to an interface first.
|
||||
itfKey = c.parseMakeInterface(key, keyType, pos)
|
||||
itfKey = b.createMakeInterface(key, keyType, pos)
|
||||
}
|
||||
params := []llvm.Value{m, itfKey, valuePtr}
|
||||
c.createRuntimeCall("hashmapInterfaceSet", params, "")
|
||||
b.createRuntimeCall("hashmapInterfaceSet", params, "")
|
||||
}
|
||||
c.emitLifetimeEnd(valuePtr, valueSize)
|
||||
b.emitLifetimeEnd(valuePtr, valueSize)
|
||||
}
|
||||
|
||||
func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
||||
// createMapDelete deletes a key from a map by calling the appropriate runtime
|
||||
// function. It is the implementation of the Go delete() builtin.
|
||||
func (b *builder) createMapDelete(keyType types.Type, m, key llvm.Value, pos token.Pos) error {
|
||||
keyType = keyType.Underlying()
|
||||
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
|
||||
// key is a string
|
||||
params := []llvm.Value{m, key}
|
||||
c.createRuntimeCall("hashmapStringDelete", params, "")
|
||||
b.createRuntimeCall("hashmapStringDelete", params, "")
|
||||
return nil
|
||||
} else if hashmapIsBinaryKey(keyType) {
|
||||
keyAlloca, keyPtr, keySize := c.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
c.builder.CreateStore(key, keyAlloca)
|
||||
keyAlloca, keyPtr, keySize := b.createTemporaryAlloca(key.Type(), "hashmap.key")
|
||||
b.CreateStore(key, keyAlloca)
|
||||
params := []llvm.Value{m, keyPtr}
|
||||
c.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||
c.emitLifetimeEnd(keyPtr, keySize)
|
||||
b.createRuntimeCall("hashmapBinaryDelete", params, "")
|
||||
b.emitLifetimeEnd(keyPtr, keySize)
|
||||
return nil
|
||||
} else {
|
||||
// Key is not trivially comparable, so compare it as an interface
|
||||
@@ -144,10 +159,10 @@ func (c *Compiler) emitMapDelete(keyType types.Type, m, key llvm.Value, pos toke
|
||||
itfKey := key
|
||||
if _, ok := keyType.(*types.Interface); !ok {
|
||||
// Not already an interface, so convert it to an interface first.
|
||||
itfKey = c.parseMakeInterface(key, keyType, pos)
|
||||
itfKey = b.createMakeInterface(key, keyType, pos)
|
||||
}
|
||||
params := []llvm.Value{m, itfKey}
|
||||
c.createRuntimeCall("hashmapInterfaceDelete", params, "")
|
||||
b.createRuntimeCall("hashmapInterfaceDelete", params, "")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/tinygo-org/tinygo/transform"
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// Run the LLVM optimizer over the module.
|
||||
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
|
||||
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) []error {
|
||||
builder := llvm.NewPassManagerBuilder()
|
||||
defer builder.Dispose()
|
||||
builder.SetOptLevel(optLevel)
|
||||
builder.SetSizeLevel(sizeLevel)
|
||||
if inlinerThreshold != 0 {
|
||||
builder.UseInlinerWithThreshold(inlinerThreshold)
|
||||
}
|
||||
builder.AddCoroutinePassesToExtensionPoints()
|
||||
|
||||
if c.PanicStrategy() == "trap" {
|
||||
transform.ReplacePanicsWithTrap(c.mod) // -panic=trap
|
||||
}
|
||||
|
||||
// run a check of all of our code
|
||||
if c.VerifyIR() {
|
||||
errs := c.checkModule()
|
||||
if errs != nil {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
|
||||
// Run function passes for each function.
|
||||
funcPasses := llvm.NewFunctionPassManagerForModule(c.mod)
|
||||
defer funcPasses.Dispose()
|
||||
builder.PopulateFunc(funcPasses)
|
||||
funcPasses.InitializeFunc()
|
||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
funcPasses.RunFunc(fn)
|
||||
}
|
||||
funcPasses.FinalizeFunc()
|
||||
|
||||
if optLevel > 0 {
|
||||
// Run some preparatory passes for the Go optimizer.
|
||||
goPasses := llvm.NewPassManager()
|
||||
defer goPasses.Dispose()
|
||||
goPasses.AddGlobalDCEPass()
|
||||
goPasses.AddGlobalOptimizerPass()
|
||||
goPasses.AddConstantPropagationPass()
|
||||
goPasses.AddAggressiveDCEPass()
|
||||
goPasses.AddFunctionAttrsPass()
|
||||
goPasses.Run(c.mod)
|
||||
|
||||
// Run Go-specific optimization passes.
|
||||
transform.OptimizeMaps(c.mod)
|
||||
transform.OptimizeStringToBytes(c.mod)
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
transform.LowerInterfaces(c.mod)
|
||||
|
||||
errs := transform.LowerInterrupts(c.mod)
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
if c.funcImplementation() == funcValueSwitch {
|
||||
transform.LowerFuncValues(c.mod)
|
||||
}
|
||||
|
||||
// After interfaces are lowered, there are many more opportunities for
|
||||
// interprocedural optimizations. To get them to work, function
|
||||
// attributes have to be updated first.
|
||||
goPasses.Run(c.mod)
|
||||
|
||||
// Run TinyGo-specific interprocedural optimizations.
|
||||
transform.OptimizeAllocs(c.mod)
|
||||
transform.OptimizeStringToBytes(c.mod)
|
||||
|
||||
// Lower runtime.isnil calls to regular nil comparisons.
|
||||
isnil := c.mod.NamedFunction("runtime.isnil")
|
||||
if !isnil.IsNil() {
|
||||
for _, use := range getUses(isnil) {
|
||||
c.builder.SetInsertPointBefore(use)
|
||||
ptr := use.Operand(0)
|
||||
if !ptr.IsABitCastInst().IsNil() {
|
||||
ptr = ptr.Operand(0)
|
||||
}
|
||||
nilptr := llvm.ConstPointerNull(ptr.Type())
|
||||
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||
use.ReplaceAllUsesWith(icmp)
|
||||
use.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
} else {
|
||||
// Must be run at any optimization level.
|
||||
transform.LowerInterfaces(c.mod)
|
||||
if c.funcImplementation() == funcValueSwitch {
|
||||
transform.LowerFuncValues(c.mod)
|
||||
}
|
||||
err := c.LowerGoroutines()
|
||||
if err != nil {
|
||||
return []error{err}
|
||||
}
|
||||
errs := transform.LowerInterrupts(c.mod)
|
||||
if len(errs) > 0 {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
if c.VerifyIR() {
|
||||
if errs := c.checkModule(); errs != nil {
|
||||
return errs
|
||||
}
|
||||
}
|
||||
if err := c.Verify(); err != nil {
|
||||
return []error{errors.New("optimizations caused a verification failure")}
|
||||
}
|
||||
|
||||
if sizeLevel >= 2 {
|
||||
// Set the "optsize" attribute to make slightly smaller binaries at the
|
||||
// cost of some performance.
|
||||
kind := llvm.AttributeKindID("optsize")
|
||||
attr := c.ctx.CreateEnumAttribute(kind, 0)
|
||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
fn.AddFunctionAttr(attr)
|
||||
}
|
||||
}
|
||||
|
||||
// After TinyGo-specific transforms have finished, undo exporting these functions.
|
||||
for _, name := range c.getFunctionsUsedInTransforms() {
|
||||
fn := c.mod.NamedFunction(name)
|
||||
if fn.IsNil() {
|
||||
continue
|
||||
}
|
||||
fn.SetLinkage(llvm.InternalLinkage)
|
||||
}
|
||||
|
||||
// Run function passes again, because without it, llvm.coro.size.i32()
|
||||
// doesn't get lowered.
|
||||
for fn := c.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
||||
funcPasses.RunFunc(fn)
|
||||
}
|
||||
funcPasses.FinalizeFunc()
|
||||
|
||||
// Run module passes.
|
||||
modPasses := llvm.NewPassManager()
|
||||
defer modPasses.Dispose()
|
||||
builder.Populate(modPasses)
|
||||
modPasses.Run(c.mod)
|
||||
|
||||
hasGCPass := transform.AddGlobalsBitmap(c.mod)
|
||||
hasGCPass = transform.MakeGCStackSlots(c.mod) || hasGCPass
|
||||
if hasGCPass {
|
||||
if err := c.Verify(); err != nil {
|
||||
return []error{errors.New("GC pass caused a verification failure")}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
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
|
||||
}
|
||||
+4
-4
@@ -10,13 +10,13 @@ import (
|
||||
// The original license can be found here:
|
||||
// https://golang.org/LICENSE
|
||||
|
||||
type StdSizes struct {
|
||||
type stdSizes struct {
|
||||
IntSize int64
|
||||
PtrSize int64
|
||||
MaxAlign int64
|
||||
}
|
||||
|
||||
func (s *StdSizes) Alignof(T types.Type) int64 {
|
||||
func (s *stdSizes) Alignof(T types.Type) int64 {
|
||||
// For arrays and structs, alignment is defined in terms
|
||||
// of alignment of the elements and fields, respectively.
|
||||
switch t := T.Underlying().(type) {
|
||||
@@ -61,7 +61,7 @@ func (s *StdSizes) Alignof(T types.Type) int64 {
|
||||
return a
|
||||
}
|
||||
|
||||
func (s *StdSizes) Offsetsof(fields []*types.Var) []int64 {
|
||||
func (s *stdSizes) Offsetsof(fields []*types.Var) []int64 {
|
||||
offsets := make([]int64, len(fields))
|
||||
var o int64
|
||||
for i, f := range fields {
|
||||
@@ -89,7 +89,7 @@ var basicSizes = [...]byte{
|
||||
types.Complex128: 16,
|
||||
}
|
||||
|
||||
func (s *StdSizes) Sizeof(T types.Type) int64 {
|
||||
func (s *stdSizes) Sizeof(T types.Type) int64 {
|
||||
switch t := T.Underlying().(type) {
|
||||
case *types.Basic:
|
||||
k := t.Kind()
|
||||
|
||||
+288
-5
@@ -15,6 +15,269 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// functionInfo contains some information about a function or method. In
|
||||
// particular, it contains information obtained from pragmas.
|
||||
//
|
||||
// The linkName value contains a valid link name, even if //go:linkname is not
|
||||
// present.
|
||||
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
|
||||
exported bool // go:export, CGo
|
||||
nobounds bool // go:nobounds
|
||||
variadic bool // go:variadic (CGo only)
|
||||
inline inlineType // go:inline
|
||||
}
|
||||
|
||||
type inlineType int
|
||||
|
||||
// How much to inline.
|
||||
const (
|
||||
// Default behavior. The compiler decides for itself whether any given
|
||||
// function will be inlined. Whether any function is inlined depends on the
|
||||
// optimization level.
|
||||
inlineDefault inlineType = iota
|
||||
|
||||
// Inline hint, just like the C inline keyword (signalled using
|
||||
// //go:inline). The compiler will be more likely to inline this function,
|
||||
// but it is not a guarantee.
|
||||
inlineHint
|
||||
|
||||
// Don't inline, just like the GCC noinline attribute. Signalled using
|
||||
// //go:noinline.
|
||||
inlineNone
|
||||
)
|
||||
|
||||
// getFunction returns the LLVM function for the given *ssa.Function, creating
|
||||
// it if needed. It can later be filled with compilerContext.createFunction().
|
||||
func (c *compilerContext) getFunction(fn *ssa.Function) llvm.Value {
|
||||
info := c.getFunctionInfo(fn)
|
||||
llvmFn := c.mod.NamedFunction(info.linkName)
|
||||
if !llvmFn.IsNil() {
|
||||
return llvmFn
|
||||
}
|
||||
|
||||
var retType llvm.Type
|
||||
if fn.Signature.Results() == nil {
|
||||
retType = c.ctx.VoidType()
|
||||
} else if fn.Signature.Results().Len() == 1 {
|
||||
retType = c.getLLVMType(fn.Signature.Results().At(0).Type())
|
||||
} else {
|
||||
results := make([]llvm.Type, 0, fn.Signature.Results().Len())
|
||||
for i := 0; i < fn.Signature.Results().Len(); i++ {
|
||||
results = append(results, c.getLLVMType(fn.Signature.Results().At(i).Type()))
|
||||
}
|
||||
retType = c.ctx.StructType(results, false)
|
||||
}
|
||||
|
||||
var paramInfos []paramInfo
|
||||
for _, param := range fn.Params {
|
||||
paramType := c.getLLVMType(param.Type())
|
||||
paramFragmentInfos := expandFormalParamType(paramType, param.Name(), param.Type())
|
||||
paramInfos = append(paramInfos, paramFragmentInfos...)
|
||||
}
|
||||
|
||||
// Add an extra parameter as the function context. This context is used in
|
||||
// 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
|
||||
for _, info := range paramInfos {
|
||||
paramTypes = append(paramTypes, info.llvmType)
|
||||
}
|
||||
|
||||
fnType := llvm.FunctionType(retType, paramTypes, info.variadic)
|
||||
llvmFn = llvm.AddFunction(c.mod, info.linkName, fnType)
|
||||
if strings.HasPrefix(c.Triple, "wasm") {
|
||||
// C functions without prototypes like this:
|
||||
// void foo();
|
||||
// are actually variadic functions. However, it appears that it has been
|
||||
// decided in WebAssembly that such prototype-less functions are not
|
||||
// allowed in WebAssembly.
|
||||
// In C, this can only happen when there are zero parameters, hence this
|
||||
// check here. For more information:
|
||||
// https://reviews.llvm.org/D48443
|
||||
// https://github.com/WebAssembly/tool-conventions/issues/16
|
||||
if info.variadic && len(fn.Params) == 0 {
|
||||
attr := c.ctx.CreateStringAttribute("no-prototype", "")
|
||||
llvmFn.AddFunctionAttr(attr)
|
||||
}
|
||||
}
|
||||
|
||||
dereferenceableOrNullKind := llvm.AttributeKindID("dereferenceable_or_null")
|
||||
for i, info := range paramInfos {
|
||||
if info.flags¶mIsDeferenceableOrNull == 0 {
|
||||
continue
|
||||
}
|
||||
if info.llvmType.TypeKind() == llvm.PointerTypeKind {
|
||||
el := info.llvmType.ElementType()
|
||||
size := c.targetData.TypeAllocSize(el)
|
||||
if size == 0 {
|
||||
// dereferenceable_or_null(0) appears to be illegal in LLVM.
|
||||
continue
|
||||
}
|
||||
dereferenceableOrNull := c.ctx.CreateEnumAttribute(dereferenceableOrNullKind, size)
|
||||
llvmFn.AddAttributeAtIndex(i+1, dereferenceableOrNull)
|
||||
}
|
||||
}
|
||||
|
||||
// Set a number of function or parameter attributes, depending on the
|
||||
// function. These functions are runtime functions that are known to have
|
||||
// certain attributes that might not be inferred by the compiler.
|
||||
switch info.linkName {
|
||||
case "abort":
|
||||
// On *nix systems, the "abort" functuion in libc is used to handle fatal panics.
|
||||
// Mark it as noreturn so LLVM can optimize away code.
|
||||
llvmFn.AddFunctionAttr(c.ctx.CreateEnumAttribute(llvm.AttributeKindID("noreturn"), 0))
|
||||
case "runtime.alloc":
|
||||
// Tell the optimizer that runtime.alloc is an allocator, meaning that it
|
||||
// returns values that are never null and never alias to an existing value.
|
||||
for _, attrName := range []string{"noalias", "nonnull"} {
|
||||
llvmFn.AddAttributeAtIndex(0, c.ctx.CreateEnumAttribute(llvm.AttributeKindID(attrName), 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
|
||||
// that the only thing we'll do is read the pointer.
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("nocapture"), 0))
|
||||
llvmFn.AddAttributeAtIndex(1, c.ctx.CreateEnumAttribute(llvm.AttributeKindID("readonly"), 0))
|
||||
}
|
||||
|
||||
// External/exported functions may not retain pointer values.
|
||||
// https://golang.org/cmd/cgo/#hdr-Passing_pointers
|
||||
if info.exported {
|
||||
// Set the wasm-import-module attribute if the function's module is set.
|
||||
if info.module != "" {
|
||||
|
||||
// We need to add the wasm-import-module and the wasm-import-name
|
||||
wasmImportModuleAttr := c.ctx.CreateStringAttribute("wasm-import-module", info.module)
|
||||
llvmFn.AddFunctionAttr(wasmImportModuleAttr)
|
||||
|
||||
// Add the Wasm Import Name, if we are a named wasm import
|
||||
if info.importName != "" {
|
||||
wasmImportNameAttr := c.ctx.CreateStringAttribute("wasm-import-name", info.importName)
|
||||
llvmFn.AddFunctionAttr(wasmImportNameAttr)
|
||||
}
|
||||
}
|
||||
nocaptureKind := llvm.AttributeKindID("nocapture")
|
||||
nocapture := c.ctx.CreateEnumAttribute(nocaptureKind, 0)
|
||||
for i, typ := range paramTypes {
|
||||
if typ.TypeKind() == llvm.PointerTypeKind {
|
||||
llvmFn.AddAttributeAtIndex(i+1, nocapture)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return llvmFn
|
||||
}
|
||||
|
||||
// getFunctionInfo returns information about a function that is not directly
|
||||
// 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 {
|
||||
// Pick the default linkName.
|
||||
info.linkName = f.RelString(nil)
|
||||
}
|
||||
// Check for //go: pragmas, which may change the link name (among others).
|
||||
info.parsePragmas(f)
|
||||
return info
|
||||
}
|
||||
|
||||
// parsePragmas is used by getFunctionInfo to parse function pragmas such as
|
||||
// //export or //go:noinline.
|
||||
func (info *functionInfo) parsePragmas(f *ssa.Function) {
|
||||
if f.Syntax() == nil {
|
||||
return
|
||||
}
|
||||
if decl, ok := f.Syntax().(*ast.FuncDecl); ok && decl.Doc != nil {
|
||||
|
||||
// Our importName for a wasm module (if we are compiling to wasm), or llvm link name
|
||||
var importName string
|
||||
|
||||
for _, comment := range decl.Doc.List {
|
||||
text := comment.Text
|
||||
if strings.HasPrefix(text, "//export ") {
|
||||
// Rewrite '//export' to '//go:export' for compatibility with
|
||||
// gc.
|
||||
text = "//go:" + text[2:]
|
||||
}
|
||||
if !strings.HasPrefix(text, "//go:") {
|
||||
continue
|
||||
}
|
||||
parts := strings.Fields(text)
|
||||
switch parts[0] {
|
||||
case "//go:export":
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
importName = parts[1]
|
||||
info.exported = true
|
||||
case "//go:wasm-module":
|
||||
// Alternative comment for setting the import module.
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
info.module = parts[1]
|
||||
case "//go:inline":
|
||||
info.inline = inlineHint
|
||||
case "//go:noinline":
|
||||
info.inline = inlineNone
|
||||
case "//go:linkname":
|
||||
if len(parts) != 3 || parts[1] != f.Name() {
|
||||
continue
|
||||
}
|
||||
// Only enable go:linkname when the package imports "unsafe".
|
||||
// This is a slightly looser requirement than what gc uses: gc
|
||||
// requires the file to import "unsafe", not the package as a
|
||||
// whole.
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.linkName = parts[2]
|
||||
}
|
||||
case "//go:nobounds":
|
||||
// Skip bounds checking in this function. Useful for some
|
||||
// runtime functions.
|
||||
// This is somewhat dangerous and thus only imported in packages
|
||||
// that import unsafe.
|
||||
if hasUnsafeImport(f.Pkg.Pkg) {
|
||||
info.nobounds = true
|
||||
}
|
||||
case "//go:variadic":
|
||||
// The //go:variadic pragma is emitted by the CGo preprocessing
|
||||
// pass for C variadic functions. This includes both explicit
|
||||
// (with ...) and implicit (no parameters in signature)
|
||||
// functions.
|
||||
if strings.HasPrefix(f.Name(), "C.") {
|
||||
// This prefix cannot naturally be created, it must have
|
||||
// been created as a result of CGo preprocessing.
|
||||
info.variadic = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the importName for our exported function if we have one
|
||||
if importName != "" {
|
||||
if info.module == "" {
|
||||
info.linkName = importName
|
||||
} else {
|
||||
// WebAssembly import
|
||||
info.importName = importName
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 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).
|
||||
@@ -26,7 +289,7 @@ type globalInfo struct {
|
||||
|
||||
// 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 *Compiler) loadASTComments(lprogram *loader.Program) {
|
||||
func (c *compilerContext) loadASTComments(lprogram *loader.Program) {
|
||||
c.astComments = map[string]*ast.CommentGroup{}
|
||||
for _, pkgInfo := range lprogram.Sorted() {
|
||||
for _, file := range pkgInfo.Files {
|
||||
@@ -56,7 +319,7 @@ func (c *Compiler) loadASTComments(lprogram *loader.Program) {
|
||||
|
||||
// getGlobal returns a LLVM IR global value for a Go SSA global. It is added to
|
||||
// the LLVM IR if it has not been added already.
|
||||
func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
|
||||
func (c *compilerContext) getGlobal(g *ssa.Global) llvm.Value {
|
||||
info := c.getGlobalInfo(g)
|
||||
llvmGlobal := c.mod.NamedGlobal(info.linkName)
|
||||
if llvmGlobal.IsNil() {
|
||||
@@ -82,11 +345,11 @@ func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Debug() && !info.extern {
|
||||
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.ir.Program.Fset.Position(g.Pos())
|
||||
pos := c.program.Fset.Position(g.Pos())
|
||||
diglobal := c.dibuilder.CreateGlobalVariableExpression(c.difiles[pos.Filename], llvm.DIGlobalVariableExpression{
|
||||
Name: g.RelString(nil),
|
||||
LinkageName: info.linkName,
|
||||
@@ -104,7 +367,7 @@ func (c *Compiler) getGlobal(g *ssa.Global) llvm.Value {
|
||||
}
|
||||
|
||||
// getGlobalInfo returns some information about a specific global.
|
||||
func (c *Compiler) getGlobalInfo(g *ssa.Global) globalInfo {
|
||||
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.
|
||||
@@ -145,3 +408,23 @@ func (info *globalInfo) parsePragmas(doc *ast.CommentGroup) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get all methods of a type.
|
||||
func getAllMethods(prog *ssa.Program, typ types.Type) []*types.Selection {
|
||||
ms := prog.MethodSets.MethodSet(typ)
|
||||
methods := make([]*types.Selection, ms.Len())
|
||||
for i := 0; i < ms.Len(); i++ {
|
||||
methods[i] = ms.At(i)
|
||||
}
|
||||
return methods
|
||||
}
|
||||
|
||||
// Return true if this package imports "unsafe", false otherwise.
|
||||
func hasUnsafeImport(pkg *types.Package) bool {
|
||||
for _, imp := range pkg.Imports() {
|
||||
if imp == types.Unsafe {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+45
-45
@@ -10,14 +10,14 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// emitSyscall emits an inline system call instruction, depending on the target
|
||||
// OS/arch.
|
||||
func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value, error) {
|
||||
num := c.getValue(frame, call.Args[0])
|
||||
// createSyscall emits an inline system call instruction, depending on the
|
||||
// target OS/arch.
|
||||
func (b *builder) createSyscall(call *ssa.CallCommon) (llvm.Value, error) {
|
||||
num := b.getValue(call.Args[0])
|
||||
var syscallResult llvm.Value
|
||||
switch {
|
||||
case c.GOARCH() == "amd64":
|
||||
if c.GOOS() == "darwin" {
|
||||
case b.GOARCH == "amd64":
|
||||
if b.GOOS == "darwin" {
|
||||
// Darwin adds this magic number to system call numbers:
|
||||
//
|
||||
// > Syscall classes for 64-bit system call entry.
|
||||
@@ -28,13 +28,13 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
// > All system classes enter the kernel via the syscall instruction.
|
||||
//
|
||||
// Source: https://opensource.apple.com/source/xnu/xnu-792.13.8/osfmk/mach/i386/syscall_sw.h
|
||||
num = c.builder.CreateOr(num, llvm.ConstInt(c.uintptrType, 0x2000000, false), "")
|
||||
num = b.CreateOr(num, llvm.ConstInt(b.uintptrType, 0x2000000, false), "")
|
||||
}
|
||||
// Sources:
|
||||
// https://stackoverflow.com/a/2538212
|
||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#syscall
|
||||
args := []llvm.Value{num}
|
||||
argTypes := []llvm.Type{c.uintptrType}
|
||||
argTypes := []llvm.Type{b.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={rax},0,{rdi},{rsi},{rdx},{r10},{r8},{r9},~{rcx},~{r11}"
|
||||
constraints := "={rax},0"
|
||||
@@ -50,21 +50,21 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
"{r12}",
|
||||
"{r13}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
llvmValue := b.getValue(arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
constraints += ",~{rcx},~{r11}"
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "syscall", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH() == "386" && c.GOOS() == "linux":
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
case b.GOARCH == "386" && b.GOOS == "linux":
|
||||
// Sources:
|
||||
// syscall(2) man page
|
||||
// https://stackoverflow.com/a/2538212
|
||||
// https://en.wikibooks.org/wiki/X86_Assembly/Interfacing_with_Linux#int_0x80
|
||||
args := []llvm.Value{num}
|
||||
argTypes := []llvm.Type{c.uintptrType}
|
||||
argTypes := []llvm.Type{b.uintptrType}
|
||||
// Constraints will look something like:
|
||||
// "={eax},0,{ebx},{ecx},{edx},{esi},{edi},{ebp}"
|
||||
constraints := "={eax},0"
|
||||
@@ -77,14 +77,14 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
"{edi}",
|
||||
"{ebp}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
llvmValue := b.getValue(arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "int 0x80", constraints, true, false, llvm.InlineAsmDialectIntel)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH() == "arm" && c.GOOS() == "linux":
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
case b.GOARCH == "arm" && b.GOOS == "linux":
|
||||
// Implement the EABI system call convention for Linux.
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
@@ -102,21 +102,21 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
"{r5}",
|
||||
"{r6}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
llvmValue := b.getValue(arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
argTypes = append(argTypes, b.uintptrType)
|
||||
constraints += ",{r7}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 4; i++ {
|
||||
// r0-r3 get clobbered after the syscall returns
|
||||
constraints += ",~{r" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
case c.GOARCH() == "arm64" && c.GOOS() == "linux":
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
case b.GOARCH == "arm64" && b.GOOS == "linux":
|
||||
// Source: syscall(2) man page.
|
||||
args := []llvm.Value{}
|
||||
argTypes := []llvm.Type{}
|
||||
@@ -132,12 +132,12 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
"{x4}",
|
||||
"{x5}",
|
||||
}[i]
|
||||
llvmValue := c.getValue(frame, arg)
|
||||
llvmValue := b.getValue(arg)
|
||||
args = append(args, llvmValue)
|
||||
argTypes = append(argTypes, llvmValue.Type())
|
||||
}
|
||||
args = append(args, num)
|
||||
argTypes = append(argTypes, c.uintptrType)
|
||||
argTypes = append(argTypes, b.uintptrType)
|
||||
constraints += ",{x8}" // syscall number
|
||||
for i := len(call.Args) - 1; i < 8; i++ {
|
||||
// x0-x7 may get clobbered during the syscall following the aarch64
|
||||
@@ -145,13 +145,13 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
constraints += ",~{x" + strconv.Itoa(i) + "}"
|
||||
}
|
||||
constraints += ",~{x16},~{x17}" // scratch registers
|
||||
fnType := llvm.FunctionType(c.uintptrType, argTypes, false)
|
||||
fnType := llvm.FunctionType(b.uintptrType, argTypes, false)
|
||||
target := llvm.InlineAsm(fnType, "svc #0", constraints, true, false, 0)
|
||||
syscallResult = c.builder.CreateCall(target, args, "")
|
||||
syscallResult = b.CreateCall(target, args, "")
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
|
||||
}
|
||||
switch c.GOOS() {
|
||||
switch b.GOOS {
|
||||
case "linux", "freebsd":
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
// Pseudocode:
|
||||
@@ -160,15 +160,15 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
// err = -syscallResult
|
||||
// }
|
||||
// return syscallResult, 0, err
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
inrange1 := c.builder.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||
inrange2 := c.builder.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(c.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
||||
hasError := c.builder.CreateAnd(inrange1, inrange2, "")
|
||||
errResult := c.builder.CreateSelect(hasError, c.builder.CreateSub(zero, syscallResult, ""), zero, "syscallError")
|
||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
zero := llvm.ConstInt(b.uintptrType, 0, false)
|
||||
inrange1 := b.CreateICmp(llvm.IntSLT, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "")
|
||||
inrange2 := b.CreateICmp(llvm.IntSGT, syscallResult, llvm.ConstInt(b.uintptrType, 0xfffffffffffff000, true), "") // -4096
|
||||
hasError := b.CreateAnd(inrange1, inrange2, "")
|
||||
errResult := b.CreateSelect(hasError, b.CreateSub(zero, syscallResult, ""), zero, "syscallError")
|
||||
retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
|
||||
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = b.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
case "darwin":
|
||||
// Return values: r0, r1 uintptr, err Errno
|
||||
@@ -178,15 +178,15 @@ func (c *Compiler) emitSyscall(frame *Frame, call *ssa.CallCommon) (llvm.Value,
|
||||
// err = syscallResult
|
||||
// }
|
||||
// return syscallResult, 0, err
|
||||
zero := llvm.ConstInt(c.uintptrType, 0, false)
|
||||
hasError := c.builder.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(c.uintptrType, 0, false), "")
|
||||
errResult := c.builder.CreateSelect(hasError, syscallResult, zero, "syscallError")
|
||||
retval := llvm.Undef(c.ctx.StructType([]llvm.Type{c.uintptrType, c.uintptrType, c.uintptrType}, false))
|
||||
retval = c.builder.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = c.builder.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = c.builder.CreateInsertValue(retval, errResult, 2, "")
|
||||
zero := llvm.ConstInt(b.uintptrType, 0, false)
|
||||
hasError := b.CreateICmp(llvm.IntNE, syscallResult, llvm.ConstInt(b.uintptrType, 0, false), "")
|
||||
errResult := b.CreateSelect(hasError, syscallResult, zero, "syscallError")
|
||||
retval := llvm.Undef(b.ctx.StructType([]llvm.Type{b.uintptrType, b.uintptrType, b.uintptrType}, false))
|
||||
retval = b.CreateInsertValue(retval, syscallResult, 0, "")
|
||||
retval = b.CreateInsertValue(retval, zero, 1, "")
|
||||
retval = b.CreateInsertValue(retval, errResult, 2, "")
|
||||
return retval, nil
|
||||
default:
|
||||
return llvm.Value{}, c.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+c.GOOS()+"/"+c.GOARCH())
|
||||
return llvm.Value{}, b.makeError(call.Pos(), "unknown GOOS/GOARCH for syscall: "+b.GOOS+"/"+b.GOARCH)
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+57
@@ -0,0 +1,57 @@
|
||||
package main
|
||||
|
||||
// Basic tests that don't need to be split into a separate file.
|
||||
|
||||
func addInt(x, y int) int {
|
||||
return x + y
|
||||
}
|
||||
|
||||
func equalInt(x, y int) bool {
|
||||
return x == y
|
||||
}
|
||||
|
||||
func floatEQ(x, y float32) bool {
|
||||
return x == y
|
||||
}
|
||||
|
||||
func floatNE(x, y float32) bool {
|
||||
return x != y
|
||||
}
|
||||
|
||||
func floatLower(x, y float32) bool {
|
||||
return x < y
|
||||
}
|
||||
|
||||
func floatLowerEqual(x, y float32) bool {
|
||||
return x <= y
|
||||
}
|
||||
|
||||
func floatGreater(x, y float32) bool {
|
||||
return x > y
|
||||
}
|
||||
|
||||
func floatGreaterEqual(x, y float32) bool {
|
||||
return x >= y
|
||||
}
|
||||
|
||||
func complexReal(x complex64) float32 {
|
||||
return real(x)
|
||||
}
|
||||
|
||||
func complexImag(x complex64) float32 {
|
||||
return imag(x)
|
||||
}
|
||||
|
||||
func complexAdd(x, y complex64) complex64 {
|
||||
return x + y
|
||||
}
|
||||
|
||||
func complexSub(x, y complex64) complex64 {
|
||||
return x - y
|
||||
}
|
||||
|
||||
func complexMul(x, y complex64) complex64 {
|
||||
return x * y
|
||||
}
|
||||
|
||||
// TODO: complexDiv (requires runtime call)
|
||||
Vendored
+98
@@ -0,0 +1,98 @@
|
||||
; 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"
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal i32 @main.addInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = add i32 %x, %y
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
define internal i1 @main.equalInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
entry:
|
||||
ret float %x.r
|
||||
}
|
||||
|
||||
define internal float @main.complexImag(float %x.r, float %x.i, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
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 {
|
||||
entry:
|
||||
%0 = fadd float %x.r, %y.r
|
||||
%1 = fadd float %x.i, %y.i
|
||||
%2 = insertvalue { float, float } undef, float %0, 0
|
||||
%3 = insertvalue { float, float } %2, float %1, 1
|
||||
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 {
|
||||
entry:
|
||||
%0 = fsub float %x.r, %y.r
|
||||
%1 = fsub float %x.i, %y.i
|
||||
%2 = insertvalue { float, float } undef, float %0, 0
|
||||
%3 = insertvalue { float, float } %2, float %1, 1
|
||||
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 {
|
||||
entry:
|
||||
%0 = fmul float %x.r, %y.r
|
||||
%1 = fmul float %x.i, %y.i
|
||||
%2 = fsub float %0, %1
|
||||
%3 = fmul float %x.r, %y.i
|
||||
%4 = fmul float %x.i, %y.r
|
||||
%5 = fadd float %3, %4
|
||||
%6 = insertvalue { float, float } undef, float %2, 0
|
||||
%7 = insertvalue { float, float } %6, float %5, 1
|
||||
ret { float, float } %7
|
||||
}
|
||||
Vendored
+39
@@ -0,0 +1,39 @@
|
||||
package main
|
||||
|
||||
// Test converting floats to ints.
|
||||
|
||||
func f32tou32(v float32) uint32 {
|
||||
return uint32(v)
|
||||
}
|
||||
|
||||
func maxu32f() float32 {
|
||||
return float32(^uint32(0))
|
||||
}
|
||||
|
||||
func maxu32tof32() uint32 {
|
||||
f := float32(^uint32(0))
|
||||
return uint32(f)
|
||||
}
|
||||
|
||||
func inftoi32() (uint32, uint32, int32, int32) {
|
||||
inf := 1.0
|
||||
inf /= 0.0
|
||||
|
||||
return uint32(inf), uint32(-inf), int32(inf), int32(-inf)
|
||||
}
|
||||
|
||||
func u32tof32tou32(v uint32) uint32 {
|
||||
return uint32(float32(v))
|
||||
}
|
||||
|
||||
func f32tou32tof32(v float32) float32 {
|
||||
return float32(uint32(v))
|
||||
}
|
||||
|
||||
func f32tou8(v float32) uint8 {
|
||||
return uint8(v)
|
||||
}
|
||||
|
||||
func f32toi8(v float32) int8 {
|
||||
return int8(v)
|
||||
}
|
||||
Vendored
+80
@@ -0,0 +1,80 @@
|
||||
; 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"
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal i32 @main.f32tou32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%positive = fcmp oge float %v, 0.000000e+00
|
||||
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
|
||||
%inbounds = and i1 %positive, %withinmax
|
||||
%saturated = sext i1 %positive to i32
|
||||
%normal = fptoui float %v to i32
|
||||
%0 = select i1 %inbounds, i32 %normal, i32 %saturated
|
||||
ret i32 %0
|
||||
}
|
||||
|
||||
define internal float @main.maxu32f(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret float 0x41F0000000000000
|
||||
}
|
||||
|
||||
define internal i32 @main.maxu32tof32(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret i32 -1
|
||||
}
|
||||
|
||||
define internal { i32, i32, i32, i32 } @main.inftoi32(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
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 {
|
||||
entry:
|
||||
%0 = uitofp i32 %v to float
|
||||
%withinmax = fcmp ole float %0, 0x41EFFFFFC0000000
|
||||
%normal = fptoui float %0 to i32
|
||||
%1 = select i1 %withinmax, i32 %normal, i32 -1
|
||||
ret i32 %1
|
||||
}
|
||||
|
||||
define internal float @main.f32tou32tof32(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%positive = fcmp oge float %v, 0.000000e+00
|
||||
%withinmax = fcmp ole float %v, 0x41EFFFFFC0000000
|
||||
%inbounds = and i1 %positive, %withinmax
|
||||
%saturated = sext i1 %positive to i32
|
||||
%normal = fptoui float %v to i32
|
||||
%0 = select i1 %inbounds, i32 %normal, i32 %saturated
|
||||
%1 = uitofp i32 %0 to float
|
||||
ret float %1
|
||||
}
|
||||
|
||||
define internal i8 @main.f32tou8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%positive = fcmp oge float %v, 0.000000e+00
|
||||
%withinmax = fcmp ole float %v, 2.550000e+02
|
||||
%inbounds = and i1 %positive, %withinmax
|
||||
%saturated = sext i1 %positive to i8
|
||||
%normal = fptoui float %v to i8
|
||||
%0 = select i1 %inbounds, i8 %normal, i8 %saturated
|
||||
ret i8 %0
|
||||
}
|
||||
|
||||
define internal i8 @main.f32toi8(float %v, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%abovemin = fcmp oge float %v, -1.280000e+02
|
||||
%belowmax = fcmp ole float %v, 1.270000e+02
|
||||
%inbounds = and i1 %abovemin, %belowmax
|
||||
%saturated = select i1 %abovemin, i8 127, i8 -128
|
||||
%isnan = fcmp uno float %v, 0.000000e+00
|
||||
%remapped = select i1 %isnan, i8 0, i8 %saturated
|
||||
%normal = fptosi float %v to i8
|
||||
%0 = select i1 %inbounds, i8 %normal, i8 %remapped
|
||||
ret i8 %0
|
||||
}
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
// This file tests various operations on pointers, such as pointer arithmetic
|
||||
// and dereferencing pointers.
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Dereference pointers.
|
||||
|
||||
func pointerDerefZero(x *[0]int) [0]int {
|
||||
return *x // This is a no-op, there is nothing to load.
|
||||
}
|
||||
|
||||
// Unsafe pointer casts, they are sometimes a no-op.
|
||||
|
||||
func pointerCastFromUnsafe(x unsafe.Pointer) *int {
|
||||
return (*int)(x)
|
||||
}
|
||||
|
||||
func pointerCastToUnsafe(x *int) unsafe.Pointer {
|
||||
return unsafe.Pointer(x)
|
||||
}
|
||||
|
||||
func pointerCastToUnsafeNoop(x *byte) unsafe.Pointer {
|
||||
return unsafe.Pointer(x)
|
||||
}
|
||||
|
||||
// The compiler has support for a few special cast+add patterns that are
|
||||
// transformed into a single GEP.
|
||||
|
||||
func pointerUnsafeGEPFixedOffset(ptr *byte) *byte {
|
||||
return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + 10))
|
||||
}
|
||||
|
||||
func pointerUnsafeGEPByteOffset(ptr *byte, offset uintptr) *byte {
|
||||
return (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + offset))
|
||||
}
|
||||
|
||||
func pointerUnsafeGEPIntOffset(ptr *int32, offset uintptr) *int32 {
|
||||
return (*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(ptr)) + offset*4))
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
; ModuleID = 'pointer.go'
|
||||
source_filename = "pointer.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"
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal [0 x i32] @main.pointerDerefZero([0 x i32]* %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret [0 x i32] zeroinitializer
|
||||
}
|
||||
|
||||
define internal i32* @main.pointerCastFromUnsafe(i8* %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = bitcast i8* %x to i32*
|
||||
ret i32* %0
|
||||
}
|
||||
|
||||
define internal i8* @main.pointerCastToUnsafe(i32* dereferenceable_or_null(4) %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = bitcast i32* %x to i8*
|
||||
ret i8* %0
|
||||
}
|
||||
|
||||
define internal i8* @main.pointerCastToUnsafeNoop(i8* dereferenceable_or_null(1) %x, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret i8* %x
|
||||
}
|
||||
|
||||
define internal i8* @main.pointerUnsafeGEPFixedOffset(i8* dereferenceable_or_null(1) %ptr, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = getelementptr inbounds i8, i8* %ptr, i32 10
|
||||
ret i8* %0
|
||||
}
|
||||
|
||||
define internal i8* @main.pointerUnsafeGEPByteOffset(i8* dereferenceable_or_null(1) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = getelementptr inbounds i8, i8* %ptr, i32 %offset
|
||||
ret i8* %0
|
||||
}
|
||||
|
||||
define internal i32* @main.pointerUnsafeGEPIntOffset(i32* dereferenceable_or_null(4) %ptr, i32 %offset, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
%0 = getelementptr i32, i32* %ptr, i32 %offset
|
||||
ret i32* %0
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
package main
|
||||
|
||||
func sliceLen(ints []int) int {
|
||||
return len(ints)
|
||||
}
|
||||
|
||||
func sliceCap(ints []int) int {
|
||||
return cap(ints)
|
||||
}
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
; ModuleID = 'slice.go'
|
||||
source_filename = "slice.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"
|
||||
|
||||
define internal void @main.init(i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal i32 @main.sliceLen(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret i32 %ints.len
|
||||
}
|
||||
|
||||
define internal i32 @main.sliceCap(i32* %ints.data, i32 %ints.len, i32 %ints.cap, i8* %context, i8* %parentHandle) unnamed_addr {
|
||||
entry:
|
||||
ret i32 %ints.cap
|
||||
}
|
||||
+13
-9
@@ -8,19 +8,23 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (c *Compiler) emitVolatileLoad(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
addr := c.getValue(frame, instr.Args[0])
|
||||
c.emitNilCheck(frame, addr, "deref")
|
||||
val := c.builder.CreateLoad(addr, "")
|
||||
// createVolatileLoad is the implementation of the intrinsic function
|
||||
// runtime/volatile.LoadT().
|
||||
func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
addr := b.getValue(instr.Args[0])
|
||||
b.createNilCheck(instr.Args[0], addr, "deref")
|
||||
val := b.CreateLoad(addr, "")
|
||||
val.SetVolatile(true)
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *Compiler) emitVolatileStore(frame *Frame, instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
addr := c.getValue(frame, instr.Args[0])
|
||||
val := c.getValue(frame, instr.Args[1])
|
||||
c.emitNilCheck(frame, addr, "deref")
|
||||
store := c.builder.CreateStore(val, addr)
|
||||
// createVolatileStore is the implementation of the intrinsic function
|
||||
// runtime/volatile.StoreT().
|
||||
func (b *builder) createVolatileStore(instr *ssa.CallCommon) (llvm.Value, error) {
|
||||
addr := b.getValue(instr.Args[0])
|
||||
val := b.getValue(instr.Args[1])
|
||||
b.createNilCheck(instr.Args[0], addr, "deref")
|
||||
store := b.CreateStore(val, addr)
|
||||
store.SetVolatile(true)
|
||||
return llvm.Value{}, nil
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ go 1.11
|
||||
|
||||
require (
|
||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2
|
||||
github.com/creack/goselect v0.1.0 // indirect
|
||||
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693
|
||||
github.com/chromedp/chromedp v0.6.4
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf
|
||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46
|
||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect
|
||||
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef
|
||||
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566
|
||||
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892
|
||||
github.com/mattn/go-colorable v0.1.8
|
||||
go.bug.st/serial v1.1.2
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78
|
||||
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2
|
||||
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f
|
||||
)
|
||||
|
||||
@@ -1,36 +1,61 @@
|
||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2 h1:oMCHnXa6CCCafdPDbMh/lWRhRByN0VFLvv+g+ayx1SI=
|
||||
github.com/blakesmith/ar v0.0.0-20150311145944-8bd4349a67f2/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
|
||||
github.com/creack/goselect v0.1.0 h1:4QiXIhcpSQF50XGaBsFzesjwX/1qOY5bOveQPmN9CXY=
|
||||
github.com/creack/goselect v0.1.0/go.mod h1:gHrIcH/9UZDn2qgeTUeW5K9eZsVYCH6/60J/FHysWyE=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693 h1:11eq/RkpaotwdF6b1TRMcdgQUPNmyFEJOB7zLvh0O/Y=
|
||||
github.com/chromedp/cdproto v0.0.0-20210113043257-dabd2f2e7693/go.mod h1:55pim6Ht4LJKdVLlyFJV/g++HsEA1hQxPbB5JyNdZC0=
|
||||
github.com/chromedp/chromedp v0.6.4 h1:Gx7ZkRyrSVmbbDDja/ieNgNGJIvElroPOyeqYQGVDSY=
|
||||
github.com/chromedp/chromedp v0.6.4/go.mod h1:vodUdJf5dF/b8n0UBJv6NeM/QK28RjP3j+eM7fq4+84=
|
||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
|
||||
github.com/creack/goselect v0.1.1 h1:tiSSgKE1eJtxs1h/VgGQWuXUP0YS4CDIFMp6vaI1ls0=
|
||||
github.com/creack/goselect v0.1.1/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
|
||||
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg=
|
||||
github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE=
|
||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46 h1:wXG2bA8fO7Vv7lLk2PihFMTqmbT173Tje39oKzQ50Mo=
|
||||
github.com/marcinbor85/gohex v0.0.0-20180128172054-7a43cd876e46/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
|
||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45 h1:mACY1anK6HNCZtm/DK2Rf2ZPHggVqeB0+7rY9Gl6wyI=
|
||||
go.bug.st/serial.v1 v0.0.0-20180827123349-5f7892a7bb45/go.mod h1:dRSl/CVCTf56CkXgJMDOdSwNfo2g1orOGE/gBGdvjZw=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892 h1:6J+qramlHVLmiBOgRiBOnQkno8uprqG6YFFQTt6uYIw=
|
||||
github.com/marcinbor85/gohex v0.0.0-20200531091804-343a4b548892/go.mod h1:Pb6XcsXyropB9LNHhnqaknG/vEwYztLkQzVCHv8sQ3M=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
go.bug.st/serial v1.0.0 h1:ogEPzrllCsnG00EqKRjeYvPRsO7NJW6DqykzkdD6E/k=
|
||||
go.bug.st/serial v1.0.0/go.mod h1:rpXPISGjuNjPTRTcMlxi9lN6LoIPxd1ixVjBd8aSk/Q=
|
||||
go.bug.st/serial v1.1.2 h1:6xDpbta8KJ+VLRTeM8ghhxXRMLE/Lr8h9iDKwydarAY=
|
||||
go.bug.st/serial v1.1.2/go.mod h1:VmYBeyJWp5BnJ0tw2NUJHZdJTGl2ecBGABHlzRK1knY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
|
||||
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78 h1:nVuTkr9L6Bq62qpUqKo/RnZCFfzDBL0bYo6w9OJUqZY=
|
||||
golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef h1:ymc9FeDom3RIEA3coKokSllBB1hRcMT0tZ1W3Jf9Ids=
|
||||
golang.org/x/tools v0.0.0-20190227180812-8dcc6e70cdef/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261 h1:rJS2Hga39YAnm7DE4qrPm6Dr/67EOojL0XPzvbEeBiw=
|
||||
tinygo.org/x/go-llvm v0.0.0-20190224120431-7707ae5d1261/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add h1:dFjMH1sLhYADg8UQm7DB56B7e+TfvAmWmEZLhyv3r/w=
|
||||
tinygo.org/x/go-llvm v0.0.0-20190818154551-95bc4ffe1add/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9 h1:d6rAX39a3C0pKrY5HcojEGyN8w9ocU0v7X28lC/TRKU=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191103182207-90b6e4bdc0b9/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2 h1:Q5Hv3e5cLMGkiYwYgZL1Zrv6nb/EY+DJpRWrdO6ws6o=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191103200204-37e93e3f04e2/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8 h1:9Bfvso+tTVQg16UzOA614NaYA4x8vsRBNtd3eBrXwp0=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191113125529-bad6d01809e8/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257 h1:o8VDylrMN7gWemBMu8rEyuogKPhcLTdx5KrUAp9macc=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191124211856-b2db3df3f257/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae h1:s8J5EyxCkHxXB08UI3gk9W9IS/ekizRvSX+PfZxnAB0=
|
||||
tinygo.org/x/go-llvm v0.0.0-20191215173731-ad71f3d24aae/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566 h1:a4y30bTf7U0zDA75v2PTL+XQ2OzJetj19gK8XwQpUNY=
|
||||
tinygo.org/x/go-llvm v0.0.0-20200104190746-1ff21df33566/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig=
|
||||
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f h1:FP5Do5omlQ/dLQ3Hfy7oyJo69VS5Hn46rZw004r0lGU=
|
||||
tinygo.org/x/go-llvm v0.0.0-20210206225315-7fe719483a0f/go.mod h1:fv1F0BSNpxMfCL0zF3M4OPFbgYHnhtB6ST0HvUtu/LE=
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package goenv
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Version of TinyGo.
|
||||
// Update this value before release of new version of software.
|
||||
const Version = "0.17.0"
|
||||
|
||||
// GetGorootVersion returns the major and minor version for a given GOROOT path.
|
||||
// If the goroot cannot be determined, (0, 0) is returned.
|
||||
func GetGorootVersion(goroot string) (major, minor int, err error) {
|
||||
s, err := GorootVersionString(goroot)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if s == "" || s[:2] != "go" {
|
||||
return 0, 0, errors.New("could not parse Go version: version does not start with 'go' prefix")
|
||||
}
|
||||
|
||||
parts := strings.Split(s[2:], ".")
|
||||
if len(parts) < 2 {
|
||||
return 0, 0, errors.New("could not parse Go version: version has less than two parts")
|
||||
}
|
||||
|
||||
// Ignore the errors, we don't really handle errors here anyway.
|
||||
var trailing string
|
||||
n, err := fmt.Sscanf(s, "go%d.%d%s", &major, &minor, &trailing)
|
||||
if n == 2 && err == io.EOF {
|
||||
// Means there were no trailing characters (i.e., not an alpha/beta)
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("failed to parse version: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GorootVersionString returns the version string as reported by the Go
|
||||
// toolchain for the given GOROOT path. It is usually of the form `go1.x.y` but
|
||||
// can have some variations (for beta releases, for example).
|
||||
func GorootVersionString(goroot string) (string, error) {
|
||||
if data, err := ioutil.ReadFile(filepath.Join(
|
||||
goroot, "src", "runtime", "internal", "sys", "zversion.go")); err == nil {
|
||||
|
||||
r := regexp.MustCompile("const TheVersion = `(.*)`")
|
||||
matches := r.FindSubmatch(data)
|
||||
if len(matches) != 2 {
|
||||
return "", errors.New("Invalid go version output:\n" + string(data))
|
||||
}
|
||||
|
||||
return string(matches[1]), nil
|
||||
|
||||
} else if data, err := ioutil.ReadFile(filepath.Join(goroot, "VERSION")); err == nil {
|
||||
return string(data), nil
|
||||
|
||||
} else {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Hooks for Docker Hub
|
||||
|
||||
Files in this directory are custom commands to be run during the different Docker Hub build phases.
|
||||
|
||||
See https://docs.docker.com/docker-hub/builds/advanced/#custom-build-phase-hooks
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
# Docker hub does a recursive clone, then checks the branch out,
|
||||
# so when a PR adds a submodule (or updates it), it fails.
|
||||
git submodule update --init
|
||||
+92
-37
@@ -6,50 +6,81 @@ possible and only run unknown expressions (e.g. external calls) at runtime. This
|
||||
is in practice a partial evaluator of the `runtime.initAll` function, which
|
||||
calls each package initializer.
|
||||
|
||||
It works by directly interpreting LLVM IR:
|
||||
This package is a rewrite of a previous partial evaluator that worked
|
||||
directly on LLVM IR and used the module and LLVM constants as intermediate
|
||||
values. This newer version instead uses a mostly Go intermediate form. It
|
||||
compiles functions and extracts relevant data first (compiler.go), then
|
||||
executes those functions (interpreter.go) in a memory space that can be
|
||||
rolled back per function (memory.go). This means that it is not necessary to
|
||||
scan functions to see whether they can be run at compile time, which was very
|
||||
error prone. Instead it just tries to execute everything and if it hits
|
||||
something it cannot interpret (such as a store to memory-mapped I/O) it rolls
|
||||
back the execution of that function and runs the function at runtime instead.
|
||||
All in all, this design provides several benefits:
|
||||
|
||||
* Almost all operations work directly on constants, and are implemented using
|
||||
the llvm.Const* set of functions that are evaluated directly.
|
||||
* External function calls and some other operations (inline assembly, volatile
|
||||
load, volatile store) are seen as having limited side effects. Limited in
|
||||
the sense that it is known at compile time which globals it affects, which
|
||||
then are marked 'dirty' (meaning, further operations on it must be done at
|
||||
runtime). These operations are emitted directly in the `runtime.initAll`
|
||||
function. Return values are also considered 'dirty'.
|
||||
* Such 'dirty' objects and local values must be executed at runtime instead of
|
||||
at compile time. This dirtyness propagates further through the IR, for
|
||||
example storing a dirty local value to a global also makes the global dirty,
|
||||
meaning that the global may not be read or written at compile time as it's
|
||||
contents at that point during interpretation is unknown.
|
||||
* There are some heuristics in place to avoid doing too much with dirty
|
||||
values. For example, a branch based on a dirty local marks the whole
|
||||
function itself as having side effect (as if it is an external function).
|
||||
However, all globals it touches are still taken into account and when a call
|
||||
is inserted in `runtime.initAll`, all globals it references are also marked
|
||||
dirty.
|
||||
* Heap allocation (`runtime.alloc`) is emulated by creating new objects. The
|
||||
value in the allocation is the initializer of the global, the zero value is
|
||||
the zero initializer.
|
||||
* Stack allocation (`alloca`) is often emulated using a fake alloca object,
|
||||
until the address of the alloca is taken in which case it is also created as
|
||||
a real `alloca` in `runtime.initAll` and marked dirty. This may be necessary
|
||||
when calling an external function with the given alloca as paramter.
|
||||
* Much better error handling. By being able to revert to runtime execution
|
||||
without the need for scanning functions, this version is able to
|
||||
automatically work around many bugs in the previous implementation.
|
||||
* More correct memory model. This is not inherent to the new design, but the
|
||||
new design also made the memory model easier to reason about.
|
||||
* Faster execution of initialization code. While it is not much faster for
|
||||
normal interpretation (maybe 25% or so) due to the compilation overhead,
|
||||
it should be a whole lot faster for loops as it doesn't have to call into
|
||||
LLVM (via CGo) for every operation.
|
||||
|
||||
As mentioned, this partial evaulator comes in three parts: a compiler, an
|
||||
interpreter, and a memory manager.
|
||||
|
||||
## Compiler
|
||||
|
||||
The main task of the compiler is that it extracts all necessary data from
|
||||
every instruction in a function so that when this instruction is interpreted,
|
||||
no additional CGo calls are necessary. This is not currently done for all
|
||||
instructions (`runtime.alloc` is a notable exception), but at least it does
|
||||
so for the vast majority of instructions.
|
||||
|
||||
## Interpreter
|
||||
|
||||
The interpreter runs an instruction just like it would if it were executed
|
||||
'for real'. The vast majority of instructions can be executed at compile
|
||||
time. As indicated above, some instructions need to be executed at runtime
|
||||
instead.
|
||||
|
||||
## Memory
|
||||
|
||||
Memory is represented as objects (the `object` type) that contains data that
|
||||
will eventually be stored in a global and values (the `value` interface) that
|
||||
can be worked with while running the interpreter. Values therefore are only
|
||||
used locally and are always passed by value (just like most LLVM constants)
|
||||
while objects represent the backing storage (like LLVM globals). Some values
|
||||
are pointer values, and point to an object.
|
||||
|
||||
Importantly, this partial evaluator can roll back the execution of a
|
||||
function. This is implemented by creating a new memory view per function
|
||||
activation, which makes sure that any change to a global (such as a store
|
||||
instruction) is stored in the memory view. It creates a copy of the object
|
||||
and stores that in the memory view to be modified. Once the function has
|
||||
executed successfully, all these modified objects are then copied into the
|
||||
parent function, up to the root function invocation which (on successful
|
||||
execution) writes the values back into the LLVM module. This way, function
|
||||
invocations can be rolled back without leaving a trace.
|
||||
|
||||
Pointer values point to memory objects, but not to a particular memory
|
||||
object. Every memory object is given an index, and pointers use that index to
|
||||
look up the current active object for the pointer to load from or to copy
|
||||
when storing to it.
|
||||
|
||||
Rolling back a function should roll back everyting, including the few
|
||||
instructions emitted at runtime. This is done by treating instructions much
|
||||
like memory objects and removing the created instructions when necessary.
|
||||
|
||||
## Why is this necessary?
|
||||
|
||||
A partial evaluator is hard to get right, so why go through all the trouble of
|
||||
writing one?
|
||||
|
||||
The main reason is that the previous attempt wasn't complete and wasn't sound.
|
||||
It simply tried to evaluate Go SSA directly, which was good but more difficult
|
||||
than necessary. An IR based interpreter needs to understand fewer instructions
|
||||
as the LLVM IR simply has less (complex) instructions than Go SSA. Also, LLVM
|
||||
provides some useful tools like easily getting all uses of a function or global,
|
||||
which Go SSA does not provide.
|
||||
|
||||
But why is it necessary at all? The answer is that globals with initializers are
|
||||
much easier to optimize by LLVM than initialization code. Also, there are a few
|
||||
other benefits:
|
||||
The answer is that globals with initializers are much easier to optimize by
|
||||
LLVM than initialization code. Also, there are a few other benefits:
|
||||
|
||||
* Dead globals are trivial to optimize away.
|
||||
* Constant globals are easier to detect. Remember that Go does not have global
|
||||
@@ -60,5 +91,29 @@ other benefits:
|
||||
* Constants are much more efficent on microcontrollers, as they can be
|
||||
allocated in flash instead of RAM.
|
||||
|
||||
The Go SSA package does not create constant initializers for globals.
|
||||
Instead, it emits initialization functions, so if you write the following:
|
||||
|
||||
```go
|
||||
var foo = []byte{1, 2, 3, 4}
|
||||
```
|
||||
|
||||
It would generate something like this:
|
||||
|
||||
```go
|
||||
var foo []byte
|
||||
|
||||
func init() {
|
||||
foo = make([]byte, 4)
|
||||
foo[0] = 1
|
||||
foo[1] = 2
|
||||
foo[2] = 3
|
||||
foo[3] = 4
|
||||
}
|
||||
```
|
||||
|
||||
This is of course hugely wasteful, it's much better to create `foo` as a
|
||||
global array instead of initializing it at runtime.
|
||||
|
||||
For more details, see [this section of the
|
||||
documentation](https://tinygo.org/compiler-internals/differences-from-go/).
|
||||
|
||||
@@ -0,0 +1,410 @@
|
||||
package interp
|
||||
|
||||
// This file compiles the LLVM IR to a form that's easy to efficiently
|
||||
// interpret.
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// A function is a compiled LLVM function, which means that interpreting it
|
||||
// avoids most CGo calls necessary. This is done in a separate step so the
|
||||
// result can be cached.
|
||||
// Functions are in SSA form, just like the LLVM version if it. The first block
|
||||
// (blocks[0]) is the entry block.
|
||||
type function struct {
|
||||
llvmFn llvm.Value
|
||||
name string // precalculated llvmFn.Name()
|
||||
params []llvm.Value // precalculated llvmFn.Params()
|
||||
blocks []*basicBlock
|
||||
locals map[llvm.Value]int
|
||||
}
|
||||
|
||||
// basicBlock represents a LLVM basic block and contains a slice of
|
||||
// instructions. The last instruction must be a terminator instruction.
|
||||
type basicBlock struct {
|
||||
instructions []instruction
|
||||
}
|
||||
|
||||
// instruction is a precompiled LLVM IR instruction. The operands can be either
|
||||
// an already known value (such as literalValue or pointerValue) but can also be
|
||||
// the special localValue, which means that the value is a function parameter or
|
||||
// is produced by another instruction in the function. In that case, the
|
||||
// interpreter will replace the operand with that local value.
|
||||
type instruction struct {
|
||||
opcode llvm.Opcode
|
||||
localIndex int
|
||||
operands []value
|
||||
llvmInst llvm.Value
|
||||
name string
|
||||
}
|
||||
|
||||
// String returns a nice human-readable version of this instruction.
|
||||
func (inst *instruction) String() string {
|
||||
operands := make([]string, len(inst.operands))
|
||||
for i, op := range inst.operands {
|
||||
operands[i] = op.String()
|
||||
}
|
||||
|
||||
name := instructionNameMap[inst.opcode]
|
||||
if name == "" {
|
||||
name = "<unknown op>"
|
||||
}
|
||||
return name + " " + strings.Join(operands, " ")
|
||||
}
|
||||
|
||||
// compileFunction compiles a given LLVM function to an easier to interpret
|
||||
// version of the function. As far as possible, all operands are preprocessed so
|
||||
// that the interpreter doesn't have to call into LLVM.
|
||||
func (r *runner) compileFunction(llvmFn llvm.Value) *function {
|
||||
fn := &function{
|
||||
llvmFn: llvmFn,
|
||||
name: llvmFn.Name(),
|
||||
params: llvmFn.Params(),
|
||||
locals: make(map[llvm.Value]int),
|
||||
}
|
||||
if llvmFn.IsDeclaration() {
|
||||
// Nothing to do.
|
||||
return fn
|
||||
}
|
||||
|
||||
for i, param := range fn.params {
|
||||
fn.locals[param] = i
|
||||
}
|
||||
|
||||
// Make a map of all the blocks, to quickly find the block number for a
|
||||
// given branch instruction.
|
||||
blockIndices := make(map[llvm.Value]int)
|
||||
for llvmBB := llvmFn.FirstBasicBlock(); !llvmBB.IsNil(); llvmBB = llvm.NextBasicBlock(llvmBB) {
|
||||
index := len(blockIndices)
|
||||
blockIndices[llvmBB.AsValue()] = index
|
||||
}
|
||||
|
||||
// Compile every block.
|
||||
for llvmBB := llvmFn.FirstBasicBlock(); !llvmBB.IsNil(); llvmBB = llvm.NextBasicBlock(llvmBB) {
|
||||
bb := &basicBlock{}
|
||||
fn.blocks = append(fn.blocks, bb)
|
||||
|
||||
// Compile every instruction in the block.
|
||||
for llvmInst := llvmBB.FirstInstruction(); !llvmInst.IsNil(); llvmInst = llvm.NextInstruction(llvmInst) {
|
||||
// Create instruction skeleton.
|
||||
opcode := llvmInst.InstructionOpcode()
|
||||
inst := instruction{
|
||||
opcode: opcode,
|
||||
localIndex: len(fn.locals),
|
||||
llvmInst: llvmInst,
|
||||
}
|
||||
fn.locals[llvmInst] = len(fn.locals)
|
||||
|
||||
// Add operands specific for this instruction.
|
||||
switch opcode {
|
||||
case llvm.Ret:
|
||||
// Return instruction, which can either be a `ret void` (no
|
||||
// return value) or return a value.
|
||||
numOperands := llvmInst.OperandsCount()
|
||||
if numOperands != 0 {
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
}
|
||||
}
|
||||
case llvm.Br:
|
||||
// Branch instruction. Can be either a conditional branch (with
|
||||
// 3 operands) or unconditional branch (with just one basic
|
||||
// block operand).
|
||||
numOperands := llvmInst.OperandsCount()
|
||||
switch numOperands {
|
||||
case 3:
|
||||
// Conditional jump to one of two blocks. Comparable to an
|
||||
// if/else in procedural languages.
|
||||
thenBB := llvmInst.Operand(2)
|
||||
elseBB := llvmInst.Operand(1)
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
literalValue{uint32(blockIndices[thenBB])},
|
||||
literalValue{uint32(blockIndices[elseBB])},
|
||||
}
|
||||
case 1:
|
||||
// Unconditional jump to a target basic block. Comparable to
|
||||
// a jump in C and Go.
|
||||
jumpBB := llvmInst.Operand(0)
|
||||
inst.operands = []value{
|
||||
literalValue{uint32(blockIndices[jumpBB])},
|
||||
}
|
||||
default:
|
||||
panic("unknown number of operands")
|
||||
}
|
||||
case llvm.PHI:
|
||||
inst.name = llvmInst.Name()
|
||||
incomingCount := inst.llvmInst.IncomingCount()
|
||||
for i := 0; i < incomingCount; i++ {
|
||||
incomingBB := inst.llvmInst.IncomingBlock(i)
|
||||
incomingValue := inst.llvmInst.IncomingValue(i)
|
||||
inst.operands = append(inst.operands,
|
||||
literalValue{uint32(blockIndices[incomingBB.AsValue()])},
|
||||
r.getValue(incomingValue),
|
||||
)
|
||||
}
|
||||
case llvm.Select:
|
||||
// Select is a special instruction that is much like a ternary
|
||||
// operator. It produces operand 1 or 2 based on the boolean
|
||||
// that is operand 0.
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
r.getValue(llvmInst.Operand(2)),
|
||||
}
|
||||
case llvm.Call:
|
||||
// Call is a regular function call but could also be a runtime
|
||||
// intrinsic. Some runtime intrinsics are treated specially by
|
||||
// the interpreter, such as runtime.alloc. We don't
|
||||
// differentiate between them here because these calls may also
|
||||
// need to be run at runtime, in which case they should all be
|
||||
// created in the same way.
|
||||
llvmCalledValue := llvmInst.CalledValue()
|
||||
if !llvmCalledValue.IsAFunction().IsNil() {
|
||||
name := llvmCalledValue.Name()
|
||||
if name == "llvm.dbg.value" || strings.HasPrefix(name, "llvm.lifetime.") {
|
||||
// These intrinsics should not be interpreted, they are not
|
||||
// relevant to the execution of this function.
|
||||
continue
|
||||
}
|
||||
}
|
||||
inst.name = llvmInst.Name()
|
||||
numOperands := llvmInst.OperandsCount()
|
||||
inst.operands = append(inst.operands, r.getValue(llvmCalledValue))
|
||||
for i := 0; i < numOperands-1; i++ {
|
||||
inst.operands = append(inst.operands, r.getValue(llvmInst.Operand(i)))
|
||||
}
|
||||
case llvm.Load:
|
||||
// Load instruction. The interpreter will load from the
|
||||
// appropriate memory view.
|
||||
// Also provide the memory size to be loaded, which is necessary
|
||||
// with a lack of type information.
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
literalValue{r.targetData.TypeAllocSize(llvmInst.Type())},
|
||||
}
|
||||
case llvm.Store:
|
||||
// Store instruction. The interpreter will create a new object
|
||||
// in the memory view of the function invocation and store to
|
||||
// that, to make it possible to roll back this store.
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
}
|
||||
case llvm.Alloca:
|
||||
// Alloca allocates stack space for local variables.
|
||||
numElements := r.getValue(inst.llvmInst.Operand(0)).(literalValue).value.(uint32)
|
||||
elementSize := r.targetData.TypeAllocSize(inst.llvmInst.Type().ElementType())
|
||||
inst.operands = []value{
|
||||
literalValue{elementSize * uint64(numElements)},
|
||||
}
|
||||
case llvm.GetElementPtr:
|
||||
// GetElementPtr does pointer arithmetic.
|
||||
inst.name = llvmInst.Name()
|
||||
ptr := llvmInst.Operand(0)
|
||||
n := llvmInst.OperandsCount()
|
||||
elementType := ptr.Type().ElementType()
|
||||
// gep: [source ptr, dest value size, pairs of indices...]
|
||||
inst.operands = []value{
|
||||
r.getValue(ptr),
|
||||
literalValue{r.targetData.TypeAllocSize(llvmInst.Type().ElementType())},
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
literalValue{r.targetData.TypeAllocSize(elementType)},
|
||||
}
|
||||
for i := 2; i < n; i++ {
|
||||
operand := r.getValue(llvmInst.Operand(i))
|
||||
if elementType.TypeKind() == llvm.StructTypeKind {
|
||||
index := operand.(literalValue).value.(uint32)
|
||||
elementOffset := r.targetData.ElementOffset(elementType, int(index))
|
||||
// Encode operands in a special way. The elementOffset
|
||||
// is just the offset in bytes. The elementSize is a
|
||||
// negative number (when cast to a int64) by flipping
|
||||
// all the bits. This allows the interpreter to detect
|
||||
// this is a struct field and that it should not
|
||||
// multiply it with the elementOffset to get the offset.
|
||||
// It is important for the interpreter to know the
|
||||
// struct field index for when the GEP must be done at
|
||||
// runtime.
|
||||
inst.operands = append(inst.operands, literalValue{elementOffset}, literalValue{^uint64(index)})
|
||||
elementType = elementType.StructElementTypes()[index]
|
||||
} else {
|
||||
elementType = elementType.ElementType()
|
||||
elementSize := r.targetData.TypeAllocSize(elementType)
|
||||
elementSizeOperand := literalValue{elementSize}
|
||||
// Add operand * elementSizeOperand bytes to the pointer.
|
||||
inst.operands = append(inst.operands, operand, elementSizeOperand)
|
||||
}
|
||||
}
|
||||
case llvm.BitCast, llvm.IntToPtr, llvm.PtrToInt:
|
||||
// Bitcasts are ususally used to cast a pointer from one type to
|
||||
// another leaving the pointer itself intact.
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
}
|
||||
case llvm.ExtractValue:
|
||||
inst.name = llvmInst.Name()
|
||||
agg := llvmInst.Operand(0)
|
||||
var offset uint64
|
||||
indexingType := agg.Type()
|
||||
for _, index := range inst.llvmInst.Indices() {
|
||||
switch indexingType.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
offset += r.targetData.ElementOffset(indexingType, int(index))
|
||||
indexingType = indexingType.StructElementTypes()[index]
|
||||
default: // ArrayTypeKind
|
||||
indexingType = indexingType.ElementType()
|
||||
elementSize := r.targetData.TypeAllocSize(indexingType)
|
||||
offset += elementSize * uint64(index)
|
||||
}
|
||||
}
|
||||
size := r.targetData.TypeAllocSize(inst.llvmInst.Type())
|
||||
// extractvalue [agg, byteOffset, byteSize]
|
||||
inst.operands = []value{
|
||||
r.getValue(agg),
|
||||
literalValue{offset},
|
||||
literalValue{size},
|
||||
}
|
||||
case llvm.InsertValue:
|
||||
inst.name = llvmInst.Name()
|
||||
agg := llvmInst.Operand(0)
|
||||
var offset uint64
|
||||
indexingType := agg.Type()
|
||||
for _, index := range inst.llvmInst.Indices() {
|
||||
switch indexingType.TypeKind() {
|
||||
case llvm.StructTypeKind:
|
||||
offset += r.targetData.ElementOffset(indexingType, int(index))
|
||||
indexingType = indexingType.StructElementTypes()[index]
|
||||
default: // ArrayTypeKind
|
||||
indexingType = indexingType.ElementType()
|
||||
elementSize := r.targetData.TypeAllocSize(indexingType)
|
||||
offset += elementSize * uint64(index)
|
||||
}
|
||||
}
|
||||
// insertvalue [agg, elt, byteOffset]
|
||||
inst.operands = []value{
|
||||
r.getValue(agg),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
literalValue{offset},
|
||||
}
|
||||
case llvm.ICmp:
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
literalValue{uint8(llvmInst.IntPredicate())},
|
||||
}
|
||||
case llvm.FCmp:
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
literalValue{uint8(llvmInst.FloatPredicate())},
|
||||
}
|
||||
case llvm.Add, llvm.Sub, llvm.Mul, llvm.UDiv, llvm.SDiv, llvm.URem, llvm.SRem, llvm.Shl, llvm.LShr, llvm.AShr, llvm.And, llvm.Or, llvm.Xor:
|
||||
// Integer binary operations.
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
r.getValue(llvmInst.Operand(1)),
|
||||
}
|
||||
case llvm.SExt, llvm.ZExt, llvm.Trunc:
|
||||
// Extend or shrink an integer size.
|
||||
// No sign extension going on so easy to do.
|
||||
// zext: [value, bitwidth]
|
||||
// trunc: [value, bitwidth]
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
literalValue{uint64(llvmInst.Type().IntTypeWidth())},
|
||||
}
|
||||
case llvm.SIToFP, llvm.UIToFP:
|
||||
// Convert an integer to a floating point instruction.
|
||||
// opcode: [value, bitwidth]
|
||||
inst.name = llvmInst.Name()
|
||||
inst.operands = []value{
|
||||
r.getValue(llvmInst.Operand(0)),
|
||||
literalValue{uint64(r.targetData.TypeAllocSize(llvmInst.Type()) * 8)},
|
||||
}
|
||||
default:
|
||||
// Unknown instruction, which is already set in inst.opcode so
|
||||
// is detectable.
|
||||
// This error is handled when actually trying to interpret this
|
||||
// instruction (to not trigger on code that won't be executed).
|
||||
}
|
||||
bb.instructions = append(bb.instructions, inst)
|
||||
}
|
||||
}
|
||||
return fn
|
||||
}
|
||||
|
||||
// instructionNameMap maps from instruction opcodes to instruction names. This
|
||||
// can be useful for debug logging.
|
||||
var instructionNameMap = [...]string{
|
||||
llvm.Ret: "ret",
|
||||
llvm.Br: "br",
|
||||
llvm.Switch: "switch",
|
||||
llvm.IndirectBr: "indirectbr",
|
||||
llvm.Invoke: "invoke",
|
||||
llvm.Unreachable: "unreachable",
|
||||
|
||||
// Standard Binary Operators
|
||||
llvm.Add: "add",
|
||||
llvm.FAdd: "fadd",
|
||||
llvm.Sub: "sub",
|
||||
llvm.FSub: "fsub",
|
||||
llvm.Mul: "mul",
|
||||
llvm.FMul: "fmul",
|
||||
llvm.UDiv: "udiv",
|
||||
llvm.SDiv: "sdiv",
|
||||
llvm.FDiv: "fdiv",
|
||||
llvm.URem: "urem",
|
||||
llvm.SRem: "srem",
|
||||
llvm.FRem: "frem",
|
||||
|
||||
// Logical Operators
|
||||
llvm.Shl: "shl",
|
||||
llvm.LShr: "lshr",
|
||||
llvm.AShr: "ashr",
|
||||
llvm.And: "and",
|
||||
llvm.Or: "or",
|
||||
llvm.Xor: "xor",
|
||||
|
||||
// Memory Operators
|
||||
llvm.Alloca: "alloca",
|
||||
llvm.Load: "load",
|
||||
llvm.Store: "store",
|
||||
llvm.GetElementPtr: "getelementptr",
|
||||
|
||||
// Cast Operators
|
||||
llvm.Trunc: "trunc",
|
||||
llvm.ZExt: "zext",
|
||||
llvm.SExt: "sext",
|
||||
llvm.FPToUI: "fptoui",
|
||||
llvm.FPToSI: "fptosi",
|
||||
llvm.UIToFP: "uitofp",
|
||||
llvm.SIToFP: "sitofp",
|
||||
llvm.FPTrunc: "fptrunc",
|
||||
llvm.FPExt: "fpext",
|
||||
llvm.PtrToInt: "ptrtoint",
|
||||
llvm.IntToPtr: "inttoptr",
|
||||
llvm.BitCast: "bitcast",
|
||||
|
||||
// Other Operators
|
||||
llvm.ICmp: "icmp",
|
||||
llvm.FCmp: "fcmp",
|
||||
llvm.PHI: "phi",
|
||||
llvm.Call: "call",
|
||||
llvm.Select: "select",
|
||||
llvm.VAArg: "vaarg",
|
||||
llvm.ExtractElement: "extractelement",
|
||||
llvm.InsertElement: "insertelement",
|
||||
llvm.ShuffleVector: "shufflevector",
|
||||
llvm.ExtractValue: "extractvalue",
|
||||
llvm.InsertValue: "insertvalue",
|
||||
}
|
||||
+29
-34
@@ -11,57 +11,52 @@ import (
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
// errUnreachable is returned when an unreachable instruction is executed. This
|
||||
// error should not be visible outside of the interp package.
|
||||
var errUnreachable = errors.New("interp: unreachable executed")
|
||||
var errLiteralToPointer = errors.New("interp: trying to convert literal value to pointer")
|
||||
|
||||
// Unsupported is the specific error that is returned when an unsupported
|
||||
// instruction is hit while trying to interpret all initializers.
|
||||
type Unsupported struct {
|
||||
ImportPath string
|
||||
Inst llvm.Value
|
||||
Pos token.Position
|
||||
// These errors are expected during normal execution and can be recovered from
|
||||
// by running the affected function at runtime instead of compile time.
|
||||
var (
|
||||
errExpectedPointer = errors.New("interp: trying to use an integer as a pointer (memory-mapped I/O?)")
|
||||
errUnsupportedInst = errors.New("interp: unsupported instruction")
|
||||
errUnsupportedRuntimeInst = errors.New("interp: unsupported instruction (to be emitted at runtime)")
|
||||
errMapAlreadyCreated = errors.New("interp: map already created")
|
||||
)
|
||||
|
||||
func isRecoverableError(err error) bool {
|
||||
return err == errExpectedPointer || err == errUnsupportedInst || err == errUnsupportedRuntimeInst || err == errMapAlreadyCreated
|
||||
}
|
||||
|
||||
func (e Unsupported) Error() string {
|
||||
// TODO: how to return the actual instruction string?
|
||||
// It looks like LLVM provides no function for that...
|
||||
return scanner.Error{
|
||||
Pos: e.Pos,
|
||||
Msg: "interp: unsupported instruction",
|
||||
}.Error()
|
||||
}
|
||||
|
||||
// unsupportedInstructionError returns a new "unsupported instruction" error for
|
||||
// the given instruction. It includes source location information, when
|
||||
// available.
|
||||
func (e *evalPackage) unsupportedInstructionError(inst llvm.Value) *Unsupported {
|
||||
return &Unsupported{
|
||||
ImportPath: e.packagePath,
|
||||
Inst: inst,
|
||||
Pos: getPosition(inst),
|
||||
}
|
||||
// ErrorLine is one line in a traceback. The position may be missing.
|
||||
type ErrorLine struct {
|
||||
Pos token.Position
|
||||
Inst llvm.Value
|
||||
}
|
||||
|
||||
// Error encapsulates compile-time interpretation errors with an associated
|
||||
// import path. The errors may not have a precise location attached.
|
||||
type Error struct {
|
||||
ImportPath string
|
||||
Errs []scanner.Error
|
||||
Inst llvm.Value
|
||||
Pos token.Position
|
||||
Err error
|
||||
Traceback []ErrorLine
|
||||
}
|
||||
|
||||
// Error returns the string of the first error in the list of errors.
|
||||
func (e Error) Error() string {
|
||||
return e.Errs[0].Error()
|
||||
func (e *Error) Error() string {
|
||||
return e.Pos.String() + ": " + e.Err.Error()
|
||||
}
|
||||
|
||||
// errorAt returns an error value for the currently interpreted package at the
|
||||
// location of the instruction. The location information may not be complete as
|
||||
// it depends on debug information in the IR.
|
||||
func (e *evalPackage) errorAt(inst llvm.Value, msg string) Error {
|
||||
return Error{
|
||||
ImportPath: e.packagePath,
|
||||
Errs: []scanner.Error{errorAt(inst, msg)},
|
||||
func (r *runner) errorAt(inst instruction, err error) *Error {
|
||||
pos := getPosition(inst.llvmInst)
|
||||
return &Error{
|
||||
ImportPath: r.pkgName,
|
||||
Pos: pos,
|
||||
Err: err,
|
||||
Traceback: []ErrorLine{{pos, inst.llvmInst}},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-647
@@ -1,647 +0,0 @@
|
||||
package interp
|
||||
|
||||
// This file implements the core interpretation routines, interpreting single
|
||||
// functions.
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type frame struct {
|
||||
*evalPackage
|
||||
fn llvm.Value
|
||||
locals map[llvm.Value]Value
|
||||
}
|
||||
|
||||
// evalBasicBlock evaluates a single basic block, returning the return value (if
|
||||
// ending with a ret instruction), a list of outgoing basic blocks (if not
|
||||
// ending with a ret instruction), or an error on failure.
|
||||
// Most of it works at compile time. Some calls get translated into calls to be
|
||||
// executed at runtime: calls to functions with side effects, external calls,
|
||||
// and operations on the result of such instructions.
|
||||
func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (retval Value, outgoing []llvm.Value, err error) {
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if fr.Debug {
|
||||
print(indent)
|
||||
inst.Dump()
|
||||
println()
|
||||
}
|
||||
switch {
|
||||
case !inst.IsABinaryOperator().IsNil():
|
||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
|
||||
switch inst.InstructionOpcode() {
|
||||
// Standard binary operators
|
||||
case llvm.Add:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateAdd(lhs, rhs, "")}
|
||||
case llvm.FAdd:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFAdd(lhs, rhs, "")}
|
||||
case llvm.Sub:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSub(lhs, rhs, "")}
|
||||
case llvm.FSub:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFSub(lhs, rhs, "")}
|
||||
case llvm.Mul:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateMul(lhs, rhs, "")}
|
||||
case llvm.FMul:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFMul(lhs, rhs, "")}
|
||||
case llvm.UDiv:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateUDiv(lhs, rhs, "")}
|
||||
case llvm.SDiv:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSDiv(lhs, rhs, "")}
|
||||
case llvm.FDiv:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFDiv(lhs, rhs, "")}
|
||||
case llvm.URem:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateURem(lhs, rhs, "")}
|
||||
case llvm.SRem:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSRem(lhs, rhs, "")}
|
||||
case llvm.FRem:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFRem(lhs, rhs, "")}
|
||||
|
||||
// Logical operators
|
||||
case llvm.Shl:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateShl(lhs, rhs, "")}
|
||||
case llvm.LShr:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateLShr(lhs, rhs, "")}
|
||||
case llvm.AShr:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateAShr(lhs, rhs, "")}
|
||||
case llvm.And:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateAnd(lhs, rhs, "")}
|
||||
case llvm.Or:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateOr(lhs, rhs, "")}
|
||||
case llvm.Xor:
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateXor(lhs, rhs, "")}
|
||||
|
||||
default:
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
|
||||
// Memory operators
|
||||
case !inst.IsAAllocaInst().IsNil():
|
||||
allocType := inst.Type().ElementType()
|
||||
alloca := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloca")
|
||||
alloca.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloca.SetLinkage(llvm.InternalLinkage)
|
||||
fr.locals[inst] = &LocalValue{
|
||||
Underlying: alloca,
|
||||
Eval: fr.Eval,
|
||||
}
|
||||
case !inst.IsALoadInst().IsNil():
|
||||
operand := fr.getLocal(inst.Operand(0)).(*LocalValue)
|
||||
var value llvm.Value
|
||||
if !operand.IsConstant() || inst.IsVolatile() || (!operand.Underlying.IsAConstantExpr().IsNil() && operand.Underlying.Opcode() == llvm.BitCast) {
|
||||
value = fr.builder.CreateLoad(operand.Value(), inst.Name())
|
||||
} else {
|
||||
value = operand.Load()
|
||||
}
|
||||
if value.Type() != inst.Type() {
|
||||
return nil, nil, fr.errorAt(inst, "interp: load: type does not match")
|
||||
}
|
||||
fr.locals[inst] = fr.getValue(value)
|
||||
case !inst.IsAStoreInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
ptr := fr.getLocal(inst.Operand(1))
|
||||
if inst.IsVolatile() {
|
||||
fr.builder.CreateStore(value.Value(), ptr.Value())
|
||||
} else {
|
||||
ptr.Store(value.Value())
|
||||
}
|
||||
case !inst.IsAGetElementPtrInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
llvmIndices := make([]llvm.Value, inst.OperandsCount()-1)
|
||||
for i := range llvmIndices {
|
||||
llvmIndices[i] = inst.Operand(i + 1)
|
||||
}
|
||||
indices := make([]uint32, len(llvmIndices))
|
||||
for i, llvmIndex := range llvmIndices {
|
||||
operand := fr.getLocal(llvmIndex)
|
||||
if !operand.IsConstant() {
|
||||
// Not a constant operation.
|
||||
// This should be detected by the scanner, but isn't at the
|
||||
// moment.
|
||||
return nil, nil, fr.errorAt(inst, "todo: non-const gep")
|
||||
}
|
||||
indices[i] = uint32(operand.Value().ZExtValue())
|
||||
}
|
||||
result, err := value.GetElementPtr(indices)
|
||||
if err != nil {
|
||||
return nil, nil, fr.errorAt(inst, err.Error())
|
||||
}
|
||||
if result.Type() != inst.Type() {
|
||||
return nil, nil, fr.errorAt(inst, "interp: gep: type does not match")
|
||||
}
|
||||
fr.locals[inst] = result
|
||||
|
||||
// Cast operators
|
||||
case !inst.IsATruncInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateTrunc(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAZExtInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateZExt(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsASExtInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSExt(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAFPToUIInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPToUI(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAFPToSIInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPToSI(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAUIToFPInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateUIToFP(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsASIToFPInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateSIToFP(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAFPTruncInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPTrunc(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAFPExtInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFPExt(value.(*LocalValue).Value(), inst.Type(), "")}
|
||||
case !inst.IsAPtrToIntInst().IsNil():
|
||||
value := fr.getLocal(inst.Operand(0))
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreatePtrToInt(value.Value(), inst.Type(), "")}
|
||||
case !inst.IsABitCastInst().IsNil() && inst.Type().TypeKind() == llvm.PointerTypeKind:
|
||||
operand := inst.Operand(0)
|
||||
if !operand.IsACallInst().IsNil() {
|
||||
fn := operand.CalledValue()
|
||||
if !fn.IsAFunction().IsNil() && fn.Name() == "runtime.alloc" {
|
||||
continue // special case: bitcast of alloc
|
||||
}
|
||||
}
|
||||
if _, ok := fr.getLocal(operand).(*MapValue); ok {
|
||||
// Special case for runtime.trackPointer calls.
|
||||
// Note: this might not be entirely sound in some rare cases
|
||||
// where the map is stored in a dirty global.
|
||||
uses := getUses(inst)
|
||||
if len(uses) == 1 {
|
||||
use := uses[0]
|
||||
if !use.IsACallInst().IsNil() && !use.CalledValue().IsAFunction().IsNil() && use.CalledValue().Name() == "runtime.trackPointer" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// It is not possible in Go to bitcast a map value to a pointer.
|
||||
return nil, nil, fr.errorAt(inst, "unimplemented: bitcast of map")
|
||||
}
|
||||
value := fr.getLocal(operand).(*LocalValue)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateBitCast(value.Value(), inst.Type(), "")}
|
||||
|
||||
// Other operators
|
||||
case !inst.IsAICmpInst().IsNil():
|
||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
predicate := inst.IntPredicate()
|
||||
if predicate == llvm.IntEQ {
|
||||
var lhsZero, rhsZero bool
|
||||
var ok1, ok2 bool
|
||||
if lhs.Type().TypeKind() == llvm.PointerTypeKind {
|
||||
// Unfortunately, the const propagation in the IR builder
|
||||
// doesn't handle pointer compares of inttoptr values. So we
|
||||
// implement it manually here.
|
||||
lhsZero, ok1 = isPointerNil(lhs)
|
||||
rhsZero, ok2 = isPointerNil(rhs)
|
||||
}
|
||||
if lhs.Type().TypeKind() == llvm.IntegerTypeKind {
|
||||
lhsZero, ok1 = isZero(lhs)
|
||||
rhsZero, ok2 = isZero(rhs)
|
||||
}
|
||||
if ok1 && ok2 {
|
||||
if lhsZero && rhsZero {
|
||||
// Both are zero, so this icmp is always evaluated to true.
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false)}
|
||||
continue
|
||||
}
|
||||
if lhsZero != rhsZero {
|
||||
// Only one of them is zero, so this comparison must return false.
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false)}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateICmp(predicate, lhs, rhs, "")}
|
||||
case !inst.IsAFCmpInst().IsNil():
|
||||
lhs := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
rhs := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
predicate := inst.FloatPredicate()
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateFCmp(predicate, lhs, rhs, "")}
|
||||
case !inst.IsAPHINode().IsNil():
|
||||
for i := 0; i < inst.IncomingCount(); i++ {
|
||||
if inst.IncomingBlock(i) == incoming {
|
||||
fr.locals[inst] = fr.getLocal(inst.IncomingValue(i))
|
||||
}
|
||||
}
|
||||
case !inst.IsACallInst().IsNil():
|
||||
callee := inst.CalledValue()
|
||||
switch {
|
||||
case callee.Name() == "runtime.alloc":
|
||||
// heap allocation
|
||||
users := getUses(inst)
|
||||
var resultInst = inst
|
||||
if len(users) == 1 && !users[0].IsABitCastInst().IsNil() {
|
||||
// happens when allocating something other than i8*
|
||||
resultInst = users[0]
|
||||
}
|
||||
size := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying.ZExtValue()
|
||||
allocType := resultInst.Type().ElementType()
|
||||
typeSize := fr.TargetData.TypeAllocSize(allocType)
|
||||
elementCount := 1
|
||||
if size != typeSize {
|
||||
// allocate an array
|
||||
if size%typeSize != 0 {
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
elementCount = int(size / typeSize)
|
||||
allocType = llvm.ArrayType(allocType, elementCount)
|
||||
}
|
||||
alloc := llvm.AddGlobal(fr.Mod, allocType, fr.packagePath+"$alloc")
|
||||
alloc.SetInitializer(llvm.ConstNull(allocType))
|
||||
alloc.SetLinkage(llvm.InternalLinkage)
|
||||
result := &LocalValue{
|
||||
Underlying: alloc,
|
||||
Eval: fr.Eval,
|
||||
}
|
||||
if elementCount == 1 {
|
||||
fr.locals[resultInst] = result
|
||||
} else {
|
||||
result, err := result.GetElementPtr([]uint32{0, 0})
|
||||
if err != nil {
|
||||
return nil, nil, errorAt(inst, err.Error())
|
||||
}
|
||||
fr.locals[resultInst] = result
|
||||
}
|
||||
case callee.Name() == "runtime.hashmapMake":
|
||||
// create a map
|
||||
keySize := inst.Operand(0).ZExtValue()
|
||||
valueSize := inst.Operand(1).ZExtValue()
|
||||
fr.locals[inst] = &MapValue{
|
||||
Eval: fr.Eval,
|
||||
PkgName: fr.packagePath,
|
||||
KeySize: int(keySize),
|
||||
ValueSize: int(valueSize),
|
||||
}
|
||||
case callee.Name() == "runtime.hashmapStringSet":
|
||||
// set a string key in the map
|
||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||
keyLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
valPtr := fr.getLocal(inst.Operand(3)).(*LocalValue)
|
||||
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
if !ok || !keyBuf.IsConstant() || !keyLen.IsConstant() || !valPtr.IsConstant() {
|
||||
// The mapassign operation could not be done at compile
|
||||
// time. Do it at runtime instead.
|
||||
m := fr.getLocal(inst.Operand(0)).Value()
|
||||
fr.markDirty(m)
|
||||
llvmParams := []llvm.Value{
|
||||
m, // *runtime.hashmap
|
||||
fr.getLocal(inst.Operand(1)).Value(), // key.ptr
|
||||
fr.getLocal(inst.Operand(2)).Value(), // key.len
|
||||
fr.getLocal(inst.Operand(3)).Value(), // value (unsafe.Pointer)
|
||||
fr.getLocal(inst.Operand(4)).Value(), // context
|
||||
fr.getLocal(inst.Operand(5)).Value(), // parentHandle
|
||||
}
|
||||
fr.builder.CreateCall(callee, llvmParams, "")
|
||||
continue
|
||||
}
|
||||
// "key" is a Go string value, which in the TinyGo calling convention is split up
|
||||
// into separate pointer and length parameters.
|
||||
m.PutString(keyBuf, keyLen, valPtr)
|
||||
case callee.Name() == "runtime.hashmapBinarySet":
|
||||
// set a binary (int etc.) key in the map
|
||||
keyBuf := fr.getLocal(inst.Operand(1)).(*LocalValue)
|
||||
valPtr := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
m, ok := fr.getLocal(inst.Operand(0)).(*MapValue)
|
||||
if !ok || !keyBuf.IsConstant() || !valPtr.IsConstant() {
|
||||
// The mapassign operation could not be done at compile
|
||||
// time. Do it at runtime instead.
|
||||
m := fr.getLocal(inst.Operand(0)).Value()
|
||||
fr.markDirty(m)
|
||||
llvmParams := []llvm.Value{
|
||||
m, // *runtime.hashmap
|
||||
fr.getLocal(inst.Operand(1)).Value(), // key
|
||||
fr.getLocal(inst.Operand(2)).Value(), // value
|
||||
fr.getLocal(inst.Operand(3)).Value(), // context
|
||||
fr.getLocal(inst.Operand(4)).Value(), // parentHandle
|
||||
}
|
||||
fr.builder.CreateCall(callee, llvmParams, "")
|
||||
continue
|
||||
}
|
||||
m.PutBinary(keyBuf, valPtr)
|
||||
case callee.Name() == "runtime.stringConcat":
|
||||
// adding two strings together
|
||||
buf1Ptr := fr.getLocal(inst.Operand(0))
|
||||
buf1Len := fr.getLocal(inst.Operand(1))
|
||||
buf2Ptr := fr.getLocal(inst.Operand(2))
|
||||
buf2Len := fr.getLocal(inst.Operand(3))
|
||||
buf1 := getStringBytes(buf1Ptr, buf1Len.Value())
|
||||
buf2 := getStringBytes(buf2Ptr, buf2Len.Value())
|
||||
result := []byte(string(buf1) + string(buf2))
|
||||
vals := make([]llvm.Value, len(result))
|
||||
for i := range vals {
|
||||
vals[i] = llvm.ConstInt(fr.Mod.Context().Int8Type(), uint64(result[i]), false)
|
||||
}
|
||||
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
|
||||
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
|
||||
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$stringconcat")
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
stringType := fr.Mod.GetTypeByName("runtime._string")
|
||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||
retLen := llvm.ConstInt(stringType.StructElementTypes()[1], uint64(len(result)), false)
|
||||
ret := llvm.ConstNull(stringType)
|
||||
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0})
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1})
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||
case callee.Name() == "runtime.sliceCopy":
|
||||
elementSize := fr.getLocal(inst.Operand(4)).(*LocalValue).Value().ZExtValue()
|
||||
dstArray := fr.getLocal(inst.Operand(0)).(*LocalValue).stripPointerCasts()
|
||||
srcArray := fr.getLocal(inst.Operand(1)).(*LocalValue).stripPointerCasts()
|
||||
dstLen := fr.getLocal(inst.Operand(2)).(*LocalValue)
|
||||
srcLen := fr.getLocal(inst.Operand(3)).(*LocalValue)
|
||||
if elementSize != 1 && dstArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind && srcArray.Type().ElementType().TypeKind() == llvm.ArrayTypeKind {
|
||||
// Slice data pointers are created by adding a global array
|
||||
// and getting the address of the first element using a GEP.
|
||||
// However, before the compiler can pass it to
|
||||
// runtime.sliceCopy, it has to perform a bitcast to a *i8,
|
||||
// to make it a unsafe.Pointer. Now, when the IR builder
|
||||
// sees a bitcast of a GEP with zero indices, it will make
|
||||
// a bitcast of the original array instead of the GEP,
|
||||
// which breaks our assumptions.
|
||||
// Re-add this GEP, in the hope that it it is then of the correct type...
|
||||
dstArrayValue, err := dstArray.GetElementPtr([]uint32{0, 0})
|
||||
if err != nil {
|
||||
return nil, nil, errorAt(inst, err.Error())
|
||||
}
|
||||
dstArray = dstArrayValue.(*LocalValue)
|
||||
srcArrayValue, err := srcArray.GetElementPtr([]uint32{0, 0})
|
||||
if err != nil {
|
||||
return nil, nil, errorAt(inst, err.Error())
|
||||
}
|
||||
srcArray = srcArrayValue.(*LocalValue)
|
||||
}
|
||||
if fr.Eval.TargetData.TypeAllocSize(dstArray.Type().ElementType()) != elementSize {
|
||||
return nil, nil, fr.errorAt(inst, "interp: slice dst element size does not match pointer type")
|
||||
}
|
||||
if fr.Eval.TargetData.TypeAllocSize(srcArray.Type().ElementType()) != elementSize {
|
||||
return nil, nil, fr.errorAt(inst, "interp: slice src element size does not match pointer type")
|
||||
}
|
||||
if dstArray.Type() != srcArray.Type() {
|
||||
return nil, nil, fr.errorAt(inst, "interp: slice element types don't match")
|
||||
}
|
||||
length := dstLen.Value().SExtValue()
|
||||
if srcLength := srcLen.Value().SExtValue(); srcLength < length {
|
||||
length = srcLength
|
||||
}
|
||||
if length < 0 {
|
||||
return nil, nil, fr.errorAt(inst, "interp: trying to copy a slice with negative length?")
|
||||
}
|
||||
for i := int64(0); i < length; i++ {
|
||||
var err error
|
||||
// *dst = *src
|
||||
dstArray.Store(srcArray.Load())
|
||||
// dst++
|
||||
dstArrayValue, err := dstArray.GetElementPtr([]uint32{1})
|
||||
if err != nil {
|
||||
return nil, nil, errorAt(inst, err.Error())
|
||||
}
|
||||
dstArray = dstArrayValue.(*LocalValue)
|
||||
// src++
|
||||
srcArrayValue, err := srcArray.GetElementPtr([]uint32{1})
|
||||
if err != nil {
|
||||
return nil, nil, errorAt(inst, err.Error())
|
||||
}
|
||||
srcArray = srcArrayValue.(*LocalValue)
|
||||
}
|
||||
case callee.Name() == "runtime.stringToBytes":
|
||||
// convert a string to a []byte
|
||||
bufPtr := fr.getLocal(inst.Operand(0))
|
||||
bufLen := fr.getLocal(inst.Operand(1))
|
||||
result := getStringBytes(bufPtr, bufLen.Value())
|
||||
vals := make([]llvm.Value, len(result))
|
||||
for i := range vals {
|
||||
vals[i] = llvm.ConstInt(fr.Mod.Context().Int8Type(), uint64(result[i]), false)
|
||||
}
|
||||
globalType := llvm.ArrayType(fr.Mod.Context().Int8Type(), len(result))
|
||||
globalValue := llvm.ConstArray(fr.Mod.Context().Int8Type(), vals)
|
||||
global := llvm.AddGlobal(fr.Mod, globalType, fr.packagePath+"$bytes")
|
||||
global.SetInitializer(globalValue)
|
||||
global.SetLinkage(llvm.InternalLinkage)
|
||||
global.SetGlobalConstant(true)
|
||||
global.SetUnnamedAddr(true)
|
||||
sliceType := inst.Type()
|
||||
retPtr := llvm.ConstGEP(global, getLLVMIndices(fr.Mod.Context().Int32Type(), []uint32{0, 0}))
|
||||
retLen := llvm.ConstInt(sliceType.StructElementTypes()[1], uint64(len(result)), false)
|
||||
ret := llvm.ConstNull(sliceType)
|
||||
ret = llvm.ConstInsertValue(ret, retPtr, []uint32{0}) // ptr
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
|
||||
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, ret}
|
||||
case callee.Name() == "runtime.interfaceImplements":
|
||||
typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
|
||||
interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
|
||||
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
|
||||
return nil, nil, fr.errorAt(inst, "interp: expected typecode to be a ptrtoint")
|
||||
}
|
||||
typecode = typecode.Operand(0)
|
||||
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
|
||||
return nil, nil, fr.errorAt(inst, "interp: expected method set in runtime.interfaceImplements to be a constant gep")
|
||||
}
|
||||
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
|
||||
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
|
||||
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
|
||||
return nil, nil, fr.errorAt(inst, "interp: expected method set to be a constant gep")
|
||||
}
|
||||
methodSet = methodSet.Operand(0).Initializer()
|
||||
|
||||
// Make a set of all the methods on the concrete type, for
|
||||
// easier checking in the next step.
|
||||
definedMethods := map[string]struct{}{}
|
||||
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
|
||||
methodInfo := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)})
|
||||
name := llvm.ConstExtractValue(methodInfo, []uint32{0}).Name()
|
||||
definedMethods[name] = struct{}{}
|
||||
}
|
||||
// Check whether all interface methods are also in the list
|
||||
// of defined methods calculated above.
|
||||
implements := uint64(1) // i1 true
|
||||
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
|
||||
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
|
||||
if _, ok := definedMethods[name]; !ok {
|
||||
// There is a method on the interface that is not
|
||||
// implemented by the type.
|
||||
implements = 0 // i1 false
|
||||
break
|
||||
}
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
|
||||
case callee.Name() == "runtime.nanotime":
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
|
||||
case callee.Name() == "llvm.dbg.value":
|
||||
// do nothing
|
||||
case strings.HasPrefix(callee.Name(), "llvm.lifetime."):
|
||||
// do nothing
|
||||
case callee.Name() == "runtime.trackPointer":
|
||||
// do nothing
|
||||
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
|
||||
// This are all print instructions, which necessarily have side
|
||||
// effects but no results.
|
||||
// TODO: print an error when executing runtime._panic (with the
|
||||
// exact error message it would print at runtime).
|
||||
var params []llvm.Value
|
||||
for i := 0; i < inst.OperandsCount()-1; i++ {
|
||||
operand := fr.getLocal(inst.Operand(i)).Value()
|
||||
fr.markDirty(operand)
|
||||
params = append(params, operand)
|
||||
}
|
||||
// TODO: accurate debug info, including call chain
|
||||
fr.builder.CreateCall(callee, params, inst.Name())
|
||||
case !callee.IsAFunction().IsNil() && callee.IsDeclaration():
|
||||
// external functions
|
||||
var params []llvm.Value
|
||||
for i := 0; i < inst.OperandsCount()-1; i++ {
|
||||
operand := fr.getLocal(inst.Operand(i)).Value()
|
||||
fr.markDirty(operand)
|
||||
params = append(params, operand)
|
||||
}
|
||||
// TODO: accurate debug info, including call chain
|
||||
result := fr.builder.CreateCall(callee, params, inst.Name())
|
||||
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
fr.markDirty(result)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, result}
|
||||
}
|
||||
case !callee.IsAFunction().IsNil():
|
||||
// regular function
|
||||
var params []Value
|
||||
dirtyParams := false
|
||||
for i := 0; i < inst.OperandsCount()-1; i++ {
|
||||
local := fr.getLocal(inst.Operand(i))
|
||||
if !local.IsConstant() {
|
||||
dirtyParams = true
|
||||
}
|
||||
params = append(params, local)
|
||||
}
|
||||
var ret Value
|
||||
scanResult, err := fr.hasSideEffects(callee)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if scanResult.severity == sideEffectLimited || dirtyParams && scanResult.severity != sideEffectAll {
|
||||
// Side effect is bounded. This means the operation invokes
|
||||
// side effects (like calling an external function) but it
|
||||
// is known at compile time which side effects it invokes.
|
||||
// This means the function can be called at runtime and the
|
||||
// affected globals can be marked dirty at compile time.
|
||||
llvmParams := make([]llvm.Value, len(params))
|
||||
for i, param := range params {
|
||||
llvmParams[i] = param.Value()
|
||||
}
|
||||
result := fr.builder.CreateCall(callee, llvmParams, inst.Name())
|
||||
ret = &LocalValue{fr.Eval, result}
|
||||
// mark all mentioned globals as dirty
|
||||
for global := range scanResult.mentionsGlobals {
|
||||
fr.markDirty(global)
|
||||
}
|
||||
} else {
|
||||
// Side effect is one of:
|
||||
// * None: no side effects, can be fully interpreted at
|
||||
// compile time.
|
||||
// * Unbounded: cannot call at runtime so we'll try to
|
||||
// interpret anyway and hope for the best.
|
||||
ret, err = fr.function(callee, params, indent+" ")
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
if inst.Type().TypeKind() != llvm.VoidTypeKind {
|
||||
fr.locals[inst] = ret
|
||||
}
|
||||
default:
|
||||
// function pointers, etc.
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
case !inst.IsAExtractValueInst().IsNil():
|
||||
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
|
||||
indices := inst.Indices()
|
||||
if agg.Underlying.IsConstant() {
|
||||
newValue := llvm.ConstExtractValue(agg.Underlying, indices)
|
||||
fr.locals[inst] = fr.getValue(newValue)
|
||||
} else {
|
||||
if len(indices) != 1 {
|
||||
return nil, nil, fr.errorAt(inst, "interp: cannot handle extractvalue with not exactly 1 index")
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateExtractValue(agg.Underlying, int(indices[0]), inst.Name())}
|
||||
}
|
||||
case !inst.IsAInsertValueInst().IsNil():
|
||||
agg := fr.getLocal(inst.Operand(0)).(*LocalValue) // must be constant
|
||||
val := fr.getLocal(inst.Operand(1))
|
||||
indices := inst.Indices()
|
||||
if agg.IsConstant() && val.IsConstant() {
|
||||
newValue := llvm.ConstInsertValue(agg.Underlying, val.Value(), indices)
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, newValue}
|
||||
} else {
|
||||
if len(indices) != 1 {
|
||||
return nil, nil, fr.errorAt(inst, "interp: cannot handle insertvalue with not exactly 1 index")
|
||||
}
|
||||
fr.locals[inst] = &LocalValue{fr.Eval, fr.builder.CreateInsertValue(agg.Underlying, val.Value(), int(indices[0]), inst.Name())}
|
||||
}
|
||||
|
||||
case !inst.IsAReturnInst().IsNil() && inst.OperandsCount() == 0:
|
||||
return nil, nil, nil // ret void
|
||||
case !inst.IsAReturnInst().IsNil() && inst.OperandsCount() == 1:
|
||||
return fr.getLocal(inst.Operand(0)), nil, nil
|
||||
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 3:
|
||||
// conditional branch (if/then/else)
|
||||
cond := fr.getLocal(inst.Operand(0)).Value()
|
||||
if cond.Type() != fr.Mod.Context().Int1Type() {
|
||||
return nil, nil, fr.errorAt(inst, "expected an i1 in a branch instruction")
|
||||
}
|
||||
thenBB := inst.Operand(1)
|
||||
elseBB := inst.Operand(2)
|
||||
if !cond.IsAInstruction().IsNil() {
|
||||
return nil, nil, fr.errorAt(inst, "interp: branch on a non-constant")
|
||||
}
|
||||
if !cond.IsAConstantExpr().IsNil() {
|
||||
// This may happen when the instruction builder could not
|
||||
// const-fold some instructions.
|
||||
return nil, nil, fr.errorAt(inst, "interp: branch on a non-const-propagated constant expression")
|
||||
}
|
||||
switch cond {
|
||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 0, false): // false
|
||||
return nil, []llvm.Value{thenBB}, nil // then
|
||||
case llvm.ConstInt(fr.Mod.Context().Int1Type(), 1, false): // true
|
||||
return nil, []llvm.Value{elseBB}, nil // else
|
||||
default:
|
||||
return nil, nil, fr.errorAt(inst, "branch was not true or false")
|
||||
}
|
||||
case !inst.IsABranchInst().IsNil() && inst.OperandsCount() == 1:
|
||||
// unconditional branch (goto)
|
||||
return nil, []llvm.Value{inst.Operand(0)}, nil
|
||||
case !inst.IsAUnreachableInst().IsNil():
|
||||
// Unreachable was reached (e.g. after a call to panic()).
|
||||
// Report this as an error, as it is not supposed to happen.
|
||||
// This is a sentinel error value.
|
||||
return nil, nil, errUnreachable
|
||||
|
||||
default:
|
||||
return nil, nil, fr.unsupportedInstructionError(inst)
|
||||
}
|
||||
}
|
||||
|
||||
panic("interp: reached end of basic block without terminator")
|
||||
}
|
||||
|
||||
// Get the Value for an operand, which is a constant value of some sort.
|
||||
func (fr *frame) getLocal(v llvm.Value) Value {
|
||||
if ret, ok := fr.locals[v]; ok {
|
||||
return ret
|
||||
} else if value := fr.getValue(v); value != nil {
|
||||
return value
|
||||
} else {
|
||||
// This should not happen under normal circumstances.
|
||||
panic("cannot find value")
|
||||
}
|
||||
}
|
||||
+100
-114
@@ -1,59 +1,77 @@
|
||||
// Package interp interprets Go package initializers as much as possible. This
|
||||
// avoid running them at runtime, improving code size and making other
|
||||
// optimizations possible.
|
||||
// Package interp is a partial evaluator of code run at package init time. See
|
||||
// the README in this package for details.
|
||||
package interp
|
||||
|
||||
// This file provides the overarching Eval object with associated (utility)
|
||||
// methods.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
type Eval struct {
|
||||
Mod llvm.Module
|
||||
TargetData llvm.TargetData
|
||||
Debug bool
|
||||
builder llvm.Builder
|
||||
dirtyGlobals map[llvm.Value]struct{}
|
||||
sideEffectFuncs map[llvm.Value]*sideEffectResult // cache of side effect scan results
|
||||
// Enable extra checks, which should be disabled by default.
|
||||
// This may help track down bugs by adding a few more sanity checks.
|
||||
const checks = true
|
||||
|
||||
// runner contains all state related to one interp run.
|
||||
type runner struct {
|
||||
mod llvm.Module
|
||||
targetData llvm.TargetData
|
||||
builder llvm.Builder
|
||||
pointerSize uint32 // cached pointer size from the TargetData
|
||||
i8ptrType llvm.Type // often used type so created in advance
|
||||
maxAlign int // maximum alignment of an object, alignment of runtime.alloc() result
|
||||
debug bool // log debug messages
|
||||
pkgName string // package name of the currently executing package
|
||||
functionCache map[llvm.Value]*function // cache of compiled functions
|
||||
objects []object // slice of objects in memory
|
||||
globals map[llvm.Value]int // map from global to index in objects slice
|
||||
start time.Time
|
||||
callsExecuted uint64
|
||||
}
|
||||
|
||||
// evalPackage encapsulates the Eval type for just a single package. The Eval
|
||||
// type keeps state across the whole program, the evalPackage type keeps extra
|
||||
// state for the currently interpreted package.
|
||||
type evalPackage struct {
|
||||
*Eval
|
||||
packagePath string
|
||||
}
|
||||
|
||||
// Run evaluates the function with the given name and then eliminates all
|
||||
// callers.
|
||||
// Run evaluates runtime.initAll function as much as possible at compile time.
|
||||
// Set debug to true if it should print output while running.
|
||||
func Run(mod llvm.Module, debug bool) error {
|
||||
if debug {
|
||||
println("\ncompile-time evaluation:")
|
||||
r := runner{
|
||||
mod: mod,
|
||||
targetData: llvm.NewTargetData(mod.DataLayout()),
|
||||
debug: debug,
|
||||
functionCache: make(map[llvm.Value]*function),
|
||||
objects: []object{{}},
|
||||
globals: make(map[llvm.Value]int),
|
||||
start: time.Now(),
|
||||
}
|
||||
r.pointerSize = uint32(r.targetData.PointerSize())
|
||||
r.i8ptrType = llvm.PointerType(mod.Context().Int8Type(), 0)
|
||||
r.maxAlign = r.targetData.PrefTypeAlignment(r.i8ptrType) // assume pointers are maximally aligned (this is not always the case)
|
||||
|
||||
name := "runtime.initAll"
|
||||
e := &Eval{
|
||||
Mod: mod,
|
||||
TargetData: llvm.NewTargetData(mod.DataLayout()),
|
||||
Debug: debug,
|
||||
dirtyGlobals: map[llvm.Value]struct{}{},
|
||||
}
|
||||
e.builder = mod.Context().NewBuilder()
|
||||
|
||||
initAll := mod.NamedFunction(name)
|
||||
initAll := mod.NamedFunction("runtime.initAll")
|
||||
bb := initAll.EntryBasicBlock()
|
||||
|
||||
// Create a builder, to insert instructions that could not be evaluated at
|
||||
// compile time.
|
||||
r.builder = mod.Context().NewBuilder()
|
||||
defer r.builder.Dispose()
|
||||
|
||||
// Create a dummy alloca in the entry block that we can set the insert point
|
||||
// to. This is necessary because otherwise we might be removing the
|
||||
// instruction (init call) that we are removing after successful
|
||||
// interpretation.
|
||||
e.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||
dummy := e.builder.CreateAlloca(e.Mod.Context().Int8Type(), "dummy")
|
||||
e.builder.SetInsertPointBefore(dummy)
|
||||
r.builder.SetInsertPointBefore(bb.FirstInstruction())
|
||||
dummy := r.builder.CreateAlloca(r.mod.Context().Int8Type(), "dummy")
|
||||
r.builder.SetInsertPointBefore(dummy)
|
||||
defer dummy.EraseFromParentAsInstruction()
|
||||
|
||||
// Get a list if init calls. A runtime.initAll might look something like this:
|
||||
// func initAll() {
|
||||
// unsafe.init()
|
||||
// machine.init()
|
||||
// runtime.init()
|
||||
// }
|
||||
// This function gets a list of these call instructions.
|
||||
var initCalls []llvm.Value
|
||||
for inst := bb.FirstInstruction(); !inst.IsNil(); inst = llvm.NextInstruction(inst) {
|
||||
if inst == dummy {
|
||||
@@ -63,99 +81,67 @@ func Run(mod llvm.Module, debug bool) error {
|
||||
break // ret void
|
||||
}
|
||||
if inst.IsACallInst().IsNil() || inst.CalledValue().IsAFunction().IsNil() {
|
||||
return errorAt(inst, "interp: expected all instructions in "+name+" to be direct calls")
|
||||
return errorAt(inst, "interp: expected all instructions in "+initAll.Name()+" to be direct calls")
|
||||
}
|
||||
initCalls = append(initCalls, inst)
|
||||
}
|
||||
|
||||
// Do this in a separate step to avoid corrupting the iterator above.
|
||||
undefPtr := llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0))
|
||||
// Run initializers for each package. Once the package initializer is
|
||||
// finished, the call to the package initializer can be removed.
|
||||
for _, call := range initCalls {
|
||||
initName := call.CalledValue().Name()
|
||||
if !strings.HasSuffix(initName, ".init") {
|
||||
return errorAt(call, "interp: expected all instructions in "+name+" to be *.init() calls")
|
||||
return errorAt(call, "interp: expected all instructions in "+initAll.Name()+" to be *.init() calls")
|
||||
}
|
||||
pkgName := initName[:len(initName)-5]
|
||||
r.pkgName = initName[:len(initName)-len(".init")]
|
||||
fn := call.CalledValue()
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, "call:", fn.Name())
|
||||
}
|
||||
_, mem, callErr := r.run(r.getFunction(fn), nil, nil, " ")
|
||||
if callErr != nil {
|
||||
if isRecoverableError(callErr.Err) {
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, "not interpretring", r.pkgName, "because of error:", callErr.Err)
|
||||
}
|
||||
mem.revert()
|
||||
continue
|
||||
}
|
||||
return callErr
|
||||
}
|
||||
call.EraseFromParentAsInstruction()
|
||||
evalPkg := evalPackage{
|
||||
Eval: e,
|
||||
packagePath: pkgName,
|
||||
for index, obj := range mem.objects {
|
||||
r.objects[index] = obj
|
||||
}
|
||||
_, err := evalPkg.function(fn, []Value{&LocalValue{e, undefPtr}, &LocalValue{e, undefPtr}}, "")
|
||||
if err == errUnreachable {
|
||||
break
|
||||
}
|
||||
r.pkgName = ""
|
||||
|
||||
// Update all global variables in the LLVM module.
|
||||
mem := memoryView{r: &r}
|
||||
for _, obj := range r.objects {
|
||||
if obj.llvmGlobal.IsNil() {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
if obj.buffer == nil {
|
||||
continue
|
||||
}
|
||||
initializer := obj.buffer.toLLVMValue(obj.llvmGlobal.Type().ElementType(), &mem)
|
||||
if checks && initializer.Type() != obj.llvmGlobal.Type().ElementType() {
|
||||
panic("initializer type mismatch")
|
||||
}
|
||||
obj.llvmGlobal.SetInitializer(initializer)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// function interprets the given function. The params are the function params
|
||||
// and the indent is the string indentation to use when dumping all interpreted
|
||||
// instructions.
|
||||
func (e *evalPackage) function(fn llvm.Value, params []Value, indent string) (Value, error) {
|
||||
fr := frame{
|
||||
evalPackage: e,
|
||||
fn: fn,
|
||||
locals: make(map[llvm.Value]Value),
|
||||
}
|
||||
for i, param := range fn.Params() {
|
||||
fr.locals[param] = params[i]
|
||||
}
|
||||
|
||||
bb := fn.EntryBasicBlock()
|
||||
var lastBB llvm.BasicBlock
|
||||
for {
|
||||
retval, outgoing, err := fr.evalBasicBlock(bb, lastBB, indent)
|
||||
if outgoing == nil {
|
||||
// returned something (a value or void, or an error)
|
||||
return retval, err
|
||||
}
|
||||
if len(outgoing) > 1 {
|
||||
panic("unimplemented: multiple outgoing blocks")
|
||||
}
|
||||
next := outgoing[0]
|
||||
if next.IsABasicBlock().IsNil() {
|
||||
panic("did not switch to a basic block")
|
||||
}
|
||||
lastBB = bb
|
||||
bb = next.AsBasicBlock()
|
||||
}
|
||||
}
|
||||
|
||||
// getValue determines what kind of LLVM value it gets and returns the
|
||||
// appropriate Value type.
|
||||
func (e *Eval) getValue(v llvm.Value) Value {
|
||||
return &LocalValue{e, v}
|
||||
}
|
||||
|
||||
// markDirty marks the passed-in LLVM value dirty, recursively. For example,
|
||||
// when it encounters a constant GEP on a global, it marks the global dirty.
|
||||
func (e *Eval) markDirty(v llvm.Value) {
|
||||
if !v.IsAGlobalVariable().IsNil() {
|
||||
if v.IsGlobalConstant() {
|
||||
return
|
||||
}
|
||||
if _, ok := e.dirtyGlobals[v]; !ok {
|
||||
e.dirtyGlobals[v] = struct{}{}
|
||||
e.sideEffectFuncs = nil // re-calculate all side effects
|
||||
}
|
||||
} else if v.IsConstant() {
|
||||
if v.OperandsCount() >= 2 && !v.Operand(0).IsAGlobalVariable().IsNil() {
|
||||
// looks like a constant getelementptr of a global.
|
||||
// TODO: find a way to make sure it really is: v.Opcode() returns 0.
|
||||
e.markDirty(v.Operand(0))
|
||||
return
|
||||
}
|
||||
return // nothing to mark
|
||||
} else if !v.IsAGetElementPtrInst().IsNil() {
|
||||
panic("interp: todo: GEP")
|
||||
} else {
|
||||
// Not constant and not a global or GEP so doesn't have to be marked
|
||||
// non-constant.
|
||||
// getFunction returns the compiled version of the given LLVM function. It
|
||||
// compiles the function if necessary and caches the result.
|
||||
func (r *runner) getFunction(llvmFn llvm.Value) *function {
|
||||
if fn, ok := r.functionCache[llvmFn]; ok {
|
||||
return fn
|
||||
}
|
||||
fn := r.compileFunction(llvmFn)
|
||||
r.functionCache[llvmFn] = fn
|
||||
return fn
|
||||
}
|
||||
|
||||
+36
-2
@@ -3,6 +3,7 @@ package interp
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -15,6 +16,7 @@ func TestInterp(t *testing.T) {
|
||||
"slice-copy",
|
||||
"consteval",
|
||||
"map",
|
||||
"interface",
|
||||
} {
|
||||
name := name // make tc local to this closure
|
||||
t.Run(name, func(t *testing.T) {
|
||||
@@ -40,9 +42,29 @@ func runTest(t *testing.T, pathPrefix string) {
|
||||
// Perform the transform.
|
||||
err = Run(mod, false)
|
||||
if err != nil {
|
||||
if err, match := err.(*Error); match {
|
||||
println(err.Error())
|
||||
if !err.Inst.IsNil() {
|
||||
err.Inst.Dump()
|
||||
println()
|
||||
}
|
||||
if len(err.Traceback) > 0 {
|
||||
println("\ntraceback:")
|
||||
for _, line := range err.Traceback {
|
||||
println(line.Pos.String() + ":")
|
||||
line.Inst.Dump()
|
||||
println()
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// To be sure, verify that the module is still valid.
|
||||
if llvm.VerifyModule(mod, llvm.PrintMessageAction) != nil {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
// Run some cleanup passes to get easy-to-read outputs.
|
||||
pm := llvm.NewPassManager()
|
||||
defer pm.Dispose()
|
||||
@@ -65,6 +87,8 @@ func runTest(t *testing.T, pathPrefix string) {
|
||||
}
|
||||
}
|
||||
|
||||
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.).
|
||||
@@ -74,8 +98,18 @@ func fuzzyEqualIR(s1, s2 string) bool {
|
||||
if len(lines1) != len(lines2) {
|
||||
return false
|
||||
}
|
||||
for i, line := range lines1 {
|
||||
if line != lines2[i] {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,917 @@
|
||||
package interp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/go-llvm"
|
||||
)
|
||||
|
||||
func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent string) (value, memoryView, *Error) {
|
||||
mem := memoryView{r: r, parent: parentMem}
|
||||
locals := make([]value, len(fn.locals))
|
||||
r.callsExecuted++
|
||||
|
||||
if time.Since(r.start) > time.Minute {
|
||||
// Running for more than a minute. This should never happen.
|
||||
return nil, mem, r.errorAt(fn.blocks[0].instructions[0], fmt.Errorf("interp: running for more than a minute, timing out (executed calls: %d)", r.callsExecuted))
|
||||
}
|
||||
|
||||
// Parameters are considered a kind of local values.
|
||||
for i, param := range params {
|
||||
locals[i] = param
|
||||
}
|
||||
|
||||
// Start with the first basic block and the first instruction.
|
||||
// Branch instructions may modify both bb and instIndex when branching.
|
||||
bb := fn.blocks[0]
|
||||
currentBB := 0
|
||||
lastBB := -1 // last basic block is undefined, only defined after a branch
|
||||
var operands []value
|
||||
for instIndex := 0; instIndex < len(bb.instructions); instIndex++ {
|
||||
inst := bb.instructions[instIndex]
|
||||
operands = operands[:0]
|
||||
isRuntimeInst := false
|
||||
if inst.opcode != llvm.PHI {
|
||||
for _, v := range inst.operands {
|
||||
if v, ok := v.(localValue); ok {
|
||||
if localVal := locals[fn.locals[v.value]]; localVal == nil {
|
||||
return nil, mem, r.errorAt(inst, errors.New("interp: local not defined"))
|
||||
} else {
|
||||
operands = append(operands, localVal)
|
||||
if _, ok := localVal.(localValue); ok {
|
||||
isRuntimeInst = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
operands = append(operands, v)
|
||||
}
|
||||
}
|
||||
if isRuntimeInst {
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
switch inst.opcode {
|
||||
case llvm.Ret:
|
||||
if len(operands) != 0 {
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"ret", operands[0])
|
||||
}
|
||||
// Return instruction has a value to return.
|
||||
return operands[0], mem, nil
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"ret")
|
||||
}
|
||||
// Return instruction doesn't return anything, it's just 'ret void'.
|
||||
return nil, mem, nil
|
||||
case llvm.Br:
|
||||
switch len(operands) {
|
||||
case 1:
|
||||
// Unconditional branch: [nextBB]
|
||||
lastBB = currentBB
|
||||
currentBB = int(operands[0].(literalValue).value.(uint32))
|
||||
bb = fn.blocks[currentBB]
|
||||
instIndex = -1 // start at 0 the next cycle
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"br", operands, "->", currentBB)
|
||||
}
|
||||
case 3:
|
||||
// Conditional branch: [cond, thenBB, elseBB]
|
||||
lastBB = currentBB
|
||||
switch operands[0].Uint() {
|
||||
case 1: // true -> thenBB
|
||||
currentBB = int(operands[1].(literalValue).value.(uint32))
|
||||
case 0: // false -> elseBB
|
||||
currentBB = int(operands[2].(literalValue).value.(uint32))
|
||||
default:
|
||||
panic("bool should be 0 or 1")
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"br", operands, "->", currentBB)
|
||||
}
|
||||
bb = fn.blocks[currentBB]
|
||||
instIndex = -1 // start at 0 the next cycle
|
||||
default:
|
||||
panic("unknown operands length")
|
||||
}
|
||||
break // continue with next block
|
||||
case llvm.PHI:
|
||||
var result value
|
||||
for i := 0; i < len(inst.operands); i += 2 {
|
||||
if int(inst.operands[i].(literalValue).value.(uint32)) == lastBB {
|
||||
incoming := inst.operands[i+1]
|
||||
if local, ok := incoming.(localValue); ok {
|
||||
result = locals[fn.locals[local.value]]
|
||||
} else {
|
||||
result = incoming
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"phi", inst.operands, "->", result)
|
||||
}
|
||||
if result == nil {
|
||||
panic("could not find PHI input")
|
||||
}
|
||||
locals[inst.localIndex] = result
|
||||
case llvm.Select:
|
||||
// Select is much like a ternary operator: it picks a result from
|
||||
// the second and third operand based on the boolean first operand.
|
||||
var result value
|
||||
switch operands[0].Uint() {
|
||||
case 1:
|
||||
result = operands[1]
|
||||
case 0:
|
||||
result = operands[2]
|
||||
default:
|
||||
panic("boolean must be 0 or 1")
|
||||
}
|
||||
locals[inst.localIndex] = result
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"select", operands, "->", result)
|
||||
}
|
||||
case llvm.Call:
|
||||
// A call instruction can either be a regular call or a runtime intrinsic.
|
||||
fnPtr, err := operands[0].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
callFn := r.getFunction(fnPtr.llvmValue(&mem))
|
||||
switch {
|
||||
case callFn.name == "runtime.trackPointer":
|
||||
// Allocas and such are created as globals, so don't need a
|
||||
// runtime.trackPointer.
|
||||
// Unless the object is allocated at runtime for example, in
|
||||
// which case this call won't even get to this point but will
|
||||
// already be emitted in initAll.
|
||||
continue
|
||||
case callFn.name == "(reflect.Type).Elem" || strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet":
|
||||
// These functions should be run at runtime. Specifically:
|
||||
// * (reflect.Type).Elem is a special function. It should
|
||||
// eventually be interpreted, but fall back to a runtime call
|
||||
// for now.
|
||||
// * Print and panic functions are best emitted directly without
|
||||
// interpreting them, otherwise we get a ton of putchar (etc.)
|
||||
// calls.
|
||||
// * runtime.hashmapGet tries to access the map value directly.
|
||||
// This is not possible as the map value is treated as a special
|
||||
// kind of object in this package.
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
case callFn.name == "runtime.nanotime" && r.pkgName == "time":
|
||||
// The time package contains a call to runtime.nanotime.
|
||||
// This appears to be to work around a limitation in Windows
|
||||
// Server 2008:
|
||||
// > Monotonic times are reported as offsets from startNano.
|
||||
// > We initialize startNano to runtimeNano() - 1 so that on systems where
|
||||
// > monotonic time resolution is fairly low (e.g. Windows 2008
|
||||
// > which appears to have a default resolution of 15ms),
|
||||
// > we avoid ever reporting a monotonic time of 0.
|
||||
// > (Callers may want to use 0 as "time not set".)
|
||||
// Simply let runtime.nanotime return 0 in this case, which
|
||||
// should be fine and avoids a call to runtime.nanotime. It
|
||||
// means that monotonic time in the time package is counted from
|
||||
// time.Time{}.Sub(1), which should be fine.
|
||||
locals[inst.localIndex] = literalValue{uint64(0)}
|
||||
case callFn.name == "runtime.alloc":
|
||||
// Allocate heap memory. At compile time, this is instead done
|
||||
// by creating a global variable.
|
||||
|
||||
// Get the requested memory size to be allocated.
|
||||
size := operands[1].Uint()
|
||||
|
||||
// Create the object.
|
||||
alloc := object{
|
||||
globalName: r.pkgName + "$alloc",
|
||||
buffer: newRawValue(uint32(size)),
|
||||
size: uint32(size),
|
||||
}
|
||||
index := len(r.objects)
|
||||
r.objects = append(r.objects, alloc)
|
||||
|
||||
// And create a pointer to this object, for working with it (so
|
||||
// that stores to it copy it, etc).
|
||||
ptr := newPointerValue(r, index, 0)
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"runtime.alloc:", size, "->", ptr)
|
||||
}
|
||||
locals[inst.localIndex] = ptr
|
||||
case callFn.name == "runtime.sliceCopy":
|
||||
// sliceCopy implements the built-in copy function for slices.
|
||||
// It is implemented here so that it can be used even if the
|
||||
// runtime implementation is not available. Doing it this way
|
||||
// may also be faster.
|
||||
// Code:
|
||||
// func sliceCopy(dst, src unsafe.Pointer, dstLen, srcLen uintptr, elemSize uintptr) int {
|
||||
// n := srcLen
|
||||
// if n > dstLen {
|
||||
// n = dstLen
|
||||
// }
|
||||
// memmove(dst, src, n*elemSize)
|
||||
// return int(n)
|
||||
// }
|
||||
dstLen := operands[3].Uint()
|
||||
srcLen := operands[4].Uint()
|
||||
elemSize := operands[5].Uint()
|
||||
n := srcLen
|
||||
if n > dstLen {
|
||||
n = dstLen
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"copy:", operands[1], operands[2], n)
|
||||
}
|
||||
if n != 0 {
|
||||
// Only try to copy bytes when there are any bytes to copy.
|
||||
// This is not just an optimization. If one of the slices
|
||||
// (or both) are nil, the asPointer method call will fail
|
||||
// even though copying a nil slice is allowed.
|
||||
dst, err := operands[1].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
src, err := operands[2].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
nBytes := uint32(n * elemSize)
|
||||
dstObj := mem.getWritable(dst.index())
|
||||
dstBuf := dstObj.buffer.asRawValue(r)
|
||||
srcBuf := mem.get(src.index()).buffer.asRawValue(r)
|
||||
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
|
||||
dstObj.buffer = dstBuf
|
||||
mem.put(dst.index(), dstObj)
|
||||
}
|
||||
switch inst.llvmInst.Type().IntTypeWidth() {
|
||||
case 16:
|
||||
locals[inst.localIndex] = literalValue{uint16(n)}
|
||||
case 32:
|
||||
locals[inst.localIndex] = literalValue{uint32(n)}
|
||||
case 64:
|
||||
locals[inst.localIndex] = literalValue{uint64(n)}
|
||||
default:
|
||||
panic("unknown integer type width")
|
||||
}
|
||||
case strings.HasPrefix(callFn.name, "llvm.memcpy.p0i8.p0i8.") || strings.HasPrefix(callFn.name, "llvm.memmove.p0i8.p0i8."):
|
||||
// Copy a block of memory from one pointer to another.
|
||||
dst, err := operands[1].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
src, err := operands[2].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
nBytes := uint32(operands[3].Uint())
|
||||
dstObj := mem.getWritable(dst.index())
|
||||
dstBuf := dstObj.buffer.asRawValue(r)
|
||||
srcBuf := mem.get(src.index()).buffer.asRawValue(r)
|
||||
copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():])
|
||||
dstObj.buffer = dstBuf
|
||||
mem.put(dst.index(), dstObj)
|
||||
case callFn.name == "runtime.typeAssert":
|
||||
// This function must be implemented manually as it is normally
|
||||
// implemented by the interface lowering pass.
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"typeassert:", operands[1:])
|
||||
}
|
||||
typeInInterfacePtr, err := operands[1].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
actualType, err := mem.load(typeInInterfacePtr, r.pointerSize).asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
assertedType, err := operands[2].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
result := assertedType.asRawValue(r).equal(actualType.asRawValue(r))
|
||||
if result {
|
||||
locals[inst.localIndex] = literalValue{uint8(1)}
|
||||
} else {
|
||||
locals[inst.localIndex] = literalValue{uint8(0)}
|
||||
}
|
||||
case callFn.name == "runtime.interfaceImplements":
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:])
|
||||
}
|
||||
|
||||
// Load various values for the interface implements check below.
|
||||
typeInInterfacePtr, err := operands[1].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
methodSetPtr, err := mem.load(typeInInterfacePtr.addOffset(r.pointerSize), r.pointerSize).asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer()
|
||||
interfaceMethodSetPtr, err := operands[2].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
interfaceMethodSet := mem.get(interfaceMethodSetPtr.index()).llvmGlobal.Initializer()
|
||||
|
||||
// Make a set of all the methods on the concrete type, for
|
||||
// easier checking in the next step.
|
||||
concreteTypeMethods := map[string]struct{}{}
|
||||
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
|
||||
methodInfo := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)})
|
||||
name := llvm.ConstExtractValue(methodInfo, []uint32{0}).Name()
|
||||
concreteTypeMethods[name] = struct{}{}
|
||||
}
|
||||
|
||||
// Check whether all interface methods are also in the list
|
||||
// of defined methods calculated above. This is the interface
|
||||
// assert itself.
|
||||
assertOk := uint8(1) // i1 true
|
||||
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
|
||||
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
|
||||
if _, ok := concreteTypeMethods[name]; !ok {
|
||||
// There is a method on the interface that is not
|
||||
// implemented by the type. The assertion will fail.
|
||||
assertOk = 0 // i1 false
|
||||
break
|
||||
}
|
||||
}
|
||||
// If assertOk is still 1, the assertion succeeded.
|
||||
locals[inst.localIndex] = literalValue{assertOk}
|
||||
case callFn.name == "runtime.hashmapMake":
|
||||
// Create a new map.
|
||||
hashmapPointerType := inst.llvmInst.Type()
|
||||
keySize := uint32(operands[1].Uint())
|
||||
valueSize := uint32(operands[2].Uint())
|
||||
m := newMapValue(r, hashmapPointerType, keySize, valueSize)
|
||||
alloc := object{
|
||||
llvmType: hashmapPointerType,
|
||||
globalName: r.pkgName + "$map",
|
||||
buffer: m,
|
||||
size: m.len(r),
|
||||
}
|
||||
index := len(r.objects)
|
||||
r.objects = append(r.objects, alloc)
|
||||
|
||||
// Create a pointer to this map. Maps are reference types, so
|
||||
// are implemented as pointers.
|
||||
ptr := newPointerValue(r, index, 0)
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapMake:", keySize, valueSize, "->", ptr)
|
||||
}
|
||||
locals[inst.localIndex] = ptr
|
||||
case callFn.name == "runtime.hashmapBinarySet":
|
||||
// Do a mapassign operation with a binary key (that is, without
|
||||
// a string key).
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
|
||||
}
|
||||
mapPtr, err := operands[1].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
|
||||
keyPtr, err := operands[2].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
valuePtr, err := operands[3].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
err = m.putBinary(&mem, keyPtr, valuePtr)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
case callFn.name == "runtime.hashmapStringSet":
|
||||
// Do a mapassign operation with a string key.
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"runtime.hashmapBinarySet:", operands[1:])
|
||||
}
|
||||
mapPtr, err := operands[1].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
m := mem.getWritable(mapPtr.index()).buffer.(*mapValue)
|
||||
stringPtr, err := operands[2].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
stringLen := operands[3].Uint()
|
||||
valuePtr, err := operands[4].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
err = m.putString(&mem, stringPtr, stringLen, valuePtr)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
default:
|
||||
if len(callFn.blocks) == 0 {
|
||||
// Call to a function declaration without a definition
|
||||
// available.
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Call a function with a definition available. Run it as usual,
|
||||
// possibly trying to recover from it if it failed to execute.
|
||||
if r.debug {
|
||||
argStrings := make([]string, len(operands)-1)
|
||||
for i := range argStrings {
|
||||
argStrings[i] = operands[i+1].String()
|
||||
}
|
||||
fmt.Fprintln(os.Stderr, indent+"call:", callFn.name+"("+strings.Join(argStrings, ", ")+")")
|
||||
}
|
||||
retval, callMem, callErr := r.run(callFn, operands[1:], &mem, indent+" ")
|
||||
if callErr != nil {
|
||||
if isRecoverableError(callErr.Err) {
|
||||
// This error can be recovered by doing the call at
|
||||
// runtime instead of at compile time. But we need to
|
||||
// revert any changes made by the call first.
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"!! revert because of error:", callErr.Err)
|
||||
}
|
||||
callMem.revert()
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Add to the traceback, so that error handling code can see
|
||||
// how this function got called.
|
||||
callErr.Traceback = append(callErr.Traceback, ErrorLine{
|
||||
Pos: getPosition(inst.llvmInst),
|
||||
Inst: inst.llvmInst,
|
||||
})
|
||||
return nil, mem, callErr
|
||||
}
|
||||
locals[inst.localIndex] = retval
|
||||
mem.extend(callMem)
|
||||
}
|
||||
case llvm.Load:
|
||||
// Load instruction, loading some data from the topmost memory view.
|
||||
ptr, err := operands[0].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
size := operands[1].(literalValue).value.(uint64)
|
||||
if mem.hasExternalStore(ptr) {
|
||||
// If there could be an external store (for example, because a
|
||||
// pointer to the object was passed to a function that could not
|
||||
// be interpreted at compile time) then the load must be done at
|
||||
// runtime.
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
result := mem.load(ptr, uint32(size))
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"load:", ptr, "->", result)
|
||||
}
|
||||
locals[inst.localIndex] = result
|
||||
case llvm.Store:
|
||||
// Store instruction. Create a new object in the memory view and
|
||||
// store to that, to make it possible to roll back this store.
|
||||
ptr, err := operands[1].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
if mem.hasExternalLoadOrStore(ptr) {
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
val := operands[0]
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"store:", val, ptr)
|
||||
}
|
||||
mem.store(val, ptr)
|
||||
case llvm.Alloca:
|
||||
// Alloca normally allocates some stack memory. In the interpreter,
|
||||
// it allocates a global instead.
|
||||
// This can likely be optimized, as all it really needs is an alloca
|
||||
// in the initAll function and creating a global is wasteful for
|
||||
// this purpose.
|
||||
|
||||
// Create the new object.
|
||||
size := operands[0].(literalValue).value.(uint64)
|
||||
alloca := object{
|
||||
llvmType: inst.llvmInst.Type(),
|
||||
globalName: r.pkgName + "$alloca",
|
||||
buffer: newRawValue(uint32(size)),
|
||||
size: uint32(size),
|
||||
}
|
||||
index := len(r.objects)
|
||||
r.objects = append(r.objects, alloca)
|
||||
|
||||
// Create a pointer to this object (an alloca produces a pointer).
|
||||
ptr := newPointerValue(r, index, 0)
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"alloca:", operands, "->", ptr)
|
||||
}
|
||||
locals[inst.localIndex] = ptr
|
||||
case llvm.GetElementPtr:
|
||||
// GetElementPtr does pointer arithmetic, changing the offset of the
|
||||
// pointer into the underlying object.
|
||||
var offset uint64
|
||||
var gepOperands []uint64
|
||||
for i := 2; i < len(operands); i += 2 {
|
||||
index := operands[i].Uint()
|
||||
elementSize := operands[i+1].Uint()
|
||||
if int64(elementSize) < 0 {
|
||||
// This is a struct field.
|
||||
// The field number is encoded by flipping all the bits.
|
||||
gepOperands = append(gepOperands, ^elementSize)
|
||||
offset += index
|
||||
} else {
|
||||
// This is a normal GEP, probably an array index.
|
||||
gepOperands = append(gepOperands, index)
|
||||
offset += elementSize * index
|
||||
}
|
||||
}
|
||||
ptr, err := operands[0].asPointer(r)
|
||||
if err != nil {
|
||||
return nil, mem, r.errorAt(inst, err)
|
||||
}
|
||||
ptr = ptr.addOffset(uint32(offset))
|
||||
locals[inst.localIndex] = ptr
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"gep:", operands, "->", ptr)
|
||||
}
|
||||
case llvm.BitCast, llvm.IntToPtr, llvm.PtrToInt:
|
||||
// Various bitcast-like instructions that all keep the same bits
|
||||
// while changing the LLVM type.
|
||||
// Because interp doesn't preserve the type, these operations are
|
||||
// identity operations.
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", operands[0])
|
||||
}
|
||||
locals[inst.localIndex] = operands[0]
|
||||
case llvm.ExtractValue:
|
||||
agg := operands[0].asRawValue(r)
|
||||
offset := operands[1].(literalValue).value.(uint64)
|
||||
size := operands[2].(literalValue).value.(uint64)
|
||||
elt := rawValue{
|
||||
buf: agg.buf[offset : offset+size],
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"extractvalue:", operands, "->", elt)
|
||||
}
|
||||
locals[inst.localIndex] = elt
|
||||
case llvm.InsertValue:
|
||||
agg := operands[0].asRawValue(r)
|
||||
elt := operands[1].asRawValue(r)
|
||||
offset := int(operands[2].(literalValue).value.(uint64))
|
||||
newagg := newRawValue(uint32(len(agg.buf)))
|
||||
copy(newagg.buf, agg.buf)
|
||||
copy(newagg.buf[offset:], elt.buf)
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"insertvalue:", operands, "->", newagg)
|
||||
}
|
||||
locals[inst.localIndex] = newagg
|
||||
case llvm.ICmp:
|
||||
predicate := llvm.IntPredicate(operands[2].(literalValue).value.(uint8))
|
||||
var result bool
|
||||
lhs := operands[0]
|
||||
rhs := operands[1]
|
||||
switch predicate {
|
||||
case llvm.IntEQ, llvm.IntNE:
|
||||
lhsPointer, lhsErr := lhs.asPointer(r)
|
||||
rhsPointer, rhsErr := rhs.asPointer(r)
|
||||
if (lhsErr == nil) != (rhsErr == nil) {
|
||||
// Fast path: only one is a pointer, so they can't be equal.
|
||||
result = false
|
||||
} else if lhsErr == nil {
|
||||
// Both must be nil, so both are pointers.
|
||||
// Compare them directly.
|
||||
result = lhsPointer.equal(rhsPointer)
|
||||
} else {
|
||||
// Fall back to generic comparison.
|
||||
result = lhs.asRawValue(r).equal(rhs.asRawValue(r))
|
||||
}
|
||||
if predicate == llvm.IntNE {
|
||||
result = !result
|
||||
}
|
||||
case llvm.IntUGT:
|
||||
result = lhs.Uint() > rhs.Uint()
|
||||
case llvm.IntUGE:
|
||||
result = lhs.Uint() >= rhs.Uint()
|
||||
case llvm.IntULT:
|
||||
result = lhs.Uint() < rhs.Uint()
|
||||
case llvm.IntULE:
|
||||
result = lhs.Uint() <= rhs.Uint()
|
||||
case llvm.IntSGT:
|
||||
result = lhs.Int() > rhs.Int()
|
||||
case llvm.IntSGE:
|
||||
result = lhs.Int() >= rhs.Int()
|
||||
case llvm.IntSLT:
|
||||
result = lhs.Int() < rhs.Int()
|
||||
case llvm.IntSLE:
|
||||
result = lhs.Int() <= rhs.Int()
|
||||
default:
|
||||
return nil, mem, r.errorAt(inst, errors.New("interp: unsupported icmp"))
|
||||
}
|
||||
if result {
|
||||
locals[inst.localIndex] = literalValue{uint8(1)}
|
||||
} else {
|
||||
locals[inst.localIndex] = literalValue{uint8(0)}
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"icmp:", operands[0], intPredicateString(predicate), operands[1], "->", result)
|
||||
}
|
||||
case llvm.FCmp:
|
||||
predicate := llvm.FloatPredicate(operands[2].(literalValue).value.(uint8))
|
||||
var result bool
|
||||
var lhs, rhs float64
|
||||
switch operands[0].len(r) {
|
||||
case 8:
|
||||
lhs = math.Float64frombits(operands[0].Uint())
|
||||
rhs = math.Float64frombits(operands[1].Uint())
|
||||
case 4:
|
||||
lhs = float64(math.Float32frombits(uint32(operands[0].Uint())))
|
||||
rhs = float64(math.Float32frombits(uint32(operands[1].Uint())))
|
||||
default:
|
||||
panic("unknown float type")
|
||||
}
|
||||
switch predicate {
|
||||
case llvm.FloatOEQ:
|
||||
result = lhs == rhs
|
||||
case llvm.FloatUNE:
|
||||
result = lhs != rhs
|
||||
case llvm.FloatOGT:
|
||||
result = lhs > rhs
|
||||
case llvm.FloatOGE:
|
||||
result = lhs >= rhs
|
||||
case llvm.FloatOLT:
|
||||
result = lhs < rhs
|
||||
case llvm.FloatOLE:
|
||||
result = lhs <= rhs
|
||||
default:
|
||||
return nil, mem, r.errorAt(inst, errors.New("interp: unsupported fcmp"))
|
||||
}
|
||||
if result {
|
||||
locals[inst.localIndex] = literalValue{uint8(1)}
|
||||
} else {
|
||||
locals[inst.localIndex] = literalValue{uint8(0)}
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+"fcmp:", operands[0], predicate, operands[1], "->", result)
|
||||
}
|
||||
case llvm.Add, llvm.Sub, llvm.Mul, llvm.UDiv, llvm.SDiv, llvm.URem, llvm.SRem, llvm.Shl, llvm.LShr, llvm.AShr, llvm.And, llvm.Or, llvm.Xor:
|
||||
// Integer binary operations.
|
||||
lhs := operands[0]
|
||||
rhs := operands[1]
|
||||
lhsPtr, err := lhs.asPointer(r)
|
||||
if err == nil {
|
||||
// The lhs is a pointer. This sometimes happens for particular
|
||||
// pointer tricks.
|
||||
switch inst.opcode {
|
||||
case llvm.Add:
|
||||
// This likely means this is part of a
|
||||
// unsafe.Pointer(uintptr(ptr) + offset) pattern.
|
||||
lhsPtr = lhsPtr.addOffset(uint32(rhs.Uint()))
|
||||
locals[inst.localIndex] = lhsPtr
|
||||
continue
|
||||
case llvm.Xor:
|
||||
if rhs.Uint() == 0 {
|
||||
// Special workaround for strings.noescape, see
|
||||
// src/strings/builder.go in the Go source tree. This is
|
||||
// the identity operator, so we can return the input.
|
||||
locals[inst.localIndex] = lhs
|
||||
continue
|
||||
}
|
||||
default:
|
||||
// Catch-all for weird operations that should just be done
|
||||
// at runtime.
|
||||
err := r.runAtRuntime(fn, inst, locals, &mem, indent)
|
||||
if err != nil {
|
||||
return nil, mem, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
var result uint64
|
||||
switch inst.opcode {
|
||||
case llvm.Add:
|
||||
result = lhs.Uint() + rhs.Uint()
|
||||
case llvm.Sub:
|
||||
result = lhs.Uint() - rhs.Uint()
|
||||
case llvm.Mul:
|
||||
result = lhs.Uint() * rhs.Uint()
|
||||
case llvm.UDiv:
|
||||
result = lhs.Uint() / rhs.Uint()
|
||||
case llvm.SDiv:
|
||||
result = uint64(lhs.Int() / rhs.Int())
|
||||
case llvm.URem:
|
||||
result = lhs.Uint() % rhs.Uint()
|
||||
case llvm.SRem:
|
||||
result = uint64(lhs.Int() % rhs.Int())
|
||||
case llvm.Shl:
|
||||
result = lhs.Uint() << rhs.Uint()
|
||||
case llvm.LShr:
|
||||
result = lhs.Uint() >> rhs.Uint()
|
||||
case llvm.AShr:
|
||||
result = uint64(lhs.Int() >> rhs.Uint())
|
||||
case llvm.And:
|
||||
result = lhs.Uint() & rhs.Uint()
|
||||
case llvm.Or:
|
||||
result = lhs.Uint() | rhs.Uint()
|
||||
case llvm.Xor:
|
||||
result = lhs.Uint() ^ rhs.Uint()
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
switch lhs.len(r) {
|
||||
case 8:
|
||||
locals[inst.localIndex] = literalValue{result}
|
||||
case 4:
|
||||
locals[inst.localIndex] = literalValue{uint32(result)}
|
||||
case 2:
|
||||
locals[inst.localIndex] = literalValue{uint16(result)}
|
||||
case 1:
|
||||
locals[inst.localIndex] = literalValue{uint8(result)}
|
||||
default:
|
||||
panic("unknown integer size")
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", lhs, rhs, "->", result)
|
||||
}
|
||||
case llvm.SExt, llvm.ZExt, llvm.Trunc:
|
||||
// Change the size of an integer to a larger or smaller bit width.
|
||||
// We make use of the fact that the Uint() function already
|
||||
// zero-extends the value and that Int() already sign-extends the
|
||||
// value, so we only need to truncate it to the appropriate bit
|
||||
// width. This means we can implement sext, zext and trunc in the
|
||||
// same way, by first {zero,sign}extending all the way up to uint64
|
||||
// and then truncating it as necessary.
|
||||
var value uint64
|
||||
if inst.opcode == llvm.SExt {
|
||||
value = uint64(operands[0].Int())
|
||||
} else {
|
||||
value = operands[0].Uint()
|
||||
}
|
||||
bitwidth := operands[1].Uint()
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth)
|
||||
}
|
||||
switch bitwidth {
|
||||
case 64:
|
||||
locals[inst.localIndex] = literalValue{value}
|
||||
case 32:
|
||||
locals[inst.localIndex] = literalValue{uint32(value)}
|
||||
case 16:
|
||||
locals[inst.localIndex] = literalValue{uint16(value)}
|
||||
case 8:
|
||||
locals[inst.localIndex] = literalValue{uint8(value)}
|
||||
default:
|
||||
panic("unknown integer size in sext/zext/trunc")
|
||||
}
|
||||
case llvm.SIToFP, llvm.UIToFP:
|
||||
var value float64
|
||||
switch inst.opcode {
|
||||
case llvm.SIToFP:
|
||||
value = float64(operands[0].Int())
|
||||
case llvm.UIToFP:
|
||||
value = float64(operands[0].Uint())
|
||||
}
|
||||
bitwidth := operands[1].Uint()
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+instructionNameMap[inst.opcode]+":", value, bitwidth)
|
||||
}
|
||||
switch bitwidth {
|
||||
case 64:
|
||||
locals[inst.localIndex] = literalValue{math.Float64bits(value)}
|
||||
case 32:
|
||||
locals[inst.localIndex] = literalValue{math.Float32bits(float32(value))}
|
||||
default:
|
||||
panic("unknown integer size in sitofp/uitofp")
|
||||
}
|
||||
default:
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+inst.String())
|
||||
}
|
||||
return nil, mem, r.errorAt(inst, errUnsupportedInst)
|
||||
}
|
||||
}
|
||||
return nil, mem, r.errorAt(bb.instructions[len(bb.instructions)-1], errors.New("interp: reached end of basic block without terminator"))
|
||||
}
|
||||
|
||||
func (r *runner) runAtRuntime(fn *function, inst instruction, locals []value, mem *memoryView, indent string) *Error {
|
||||
numOperands := inst.llvmInst.OperandsCount()
|
||||
operands := make([]llvm.Value, numOperands)
|
||||
for i := 0; i < numOperands; i++ {
|
||||
operand := inst.llvmInst.Operand(i)
|
||||
if !operand.IsAInstruction().IsNil() || !operand.IsAArgument().IsNil() {
|
||||
operand = locals[fn.locals[operand]].toLLVMValue(operand.Type(), mem)
|
||||
}
|
||||
operands[i] = operand
|
||||
}
|
||||
if r.debug {
|
||||
fmt.Fprintln(os.Stderr, indent+inst.String())
|
||||
}
|
||||
var result llvm.Value
|
||||
switch inst.opcode {
|
||||
case llvm.Call:
|
||||
llvmFn := operands[len(operands)-1]
|
||||
args := operands[:len(operands)-1]
|
||||
for _, arg := range args {
|
||||
if arg.Type().TypeKind() == llvm.PointerTypeKind {
|
||||
mem.markExternalStore(arg)
|
||||
}
|
||||
}
|
||||
result = r.builder.CreateCall(llvmFn, args, inst.name)
|
||||
case llvm.Load:
|
||||
mem.markExternalLoad(operands[0])
|
||||
result = r.builder.CreateLoad(operands[0], inst.name)
|
||||
if inst.llvmInst.IsVolatile() {
|
||||
result.SetVolatile(true)
|
||||
}
|
||||
case llvm.Store:
|
||||
mem.markExternalStore(operands[1])
|
||||
result = r.builder.CreateStore(operands[0], operands[1])
|
||||
if inst.llvmInst.IsVolatile() {
|
||||
result.SetVolatile(true)
|
||||
}
|
||||
case llvm.BitCast:
|
||||
result = r.builder.CreateBitCast(operands[0], inst.llvmInst.Type(), inst.name)
|
||||
case llvm.ExtractValue:
|
||||
indices := inst.llvmInst.Indices()
|
||||
if len(indices) != 1 {
|
||||
panic("expected exactly one index")
|
||||
}
|
||||
result = r.builder.CreateExtractValue(operands[0], int(indices[0]), inst.name)
|
||||
case llvm.InsertValue:
|
||||
indices := inst.llvmInst.Indices()
|
||||
if len(indices) != 1 {
|
||||
panic("expected exactly one index")
|
||||
}
|
||||
result = r.builder.CreateInsertValue(operands[0], operands[1], int(indices[0]), inst.name)
|
||||
case llvm.Add:
|
||||
result = r.builder.CreateAdd(operands[0], operands[1], inst.name)
|
||||
case llvm.Sub:
|
||||
result = r.builder.CreateSub(operands[0], operands[1], inst.name)
|
||||
case llvm.Mul:
|
||||
result = r.builder.CreateMul(operands[0], operands[1], inst.name)
|
||||
case llvm.UDiv:
|
||||
result = r.builder.CreateUDiv(operands[0], operands[1], inst.name)
|
||||
case llvm.SDiv:
|
||||
result = r.builder.CreateSDiv(operands[0], operands[1], inst.name)
|
||||
case llvm.URem:
|
||||
result = r.builder.CreateURem(operands[0], operands[1], inst.name)
|
||||
case llvm.SRem:
|
||||
result = r.builder.CreateSRem(operands[0], operands[1], inst.name)
|
||||
case llvm.ZExt:
|
||||
result = r.builder.CreateZExt(operands[0], inst.llvmInst.Type(), inst.name)
|
||||
default:
|
||||
return r.errorAt(inst, errUnsupportedRuntimeInst)
|
||||
}
|
||||
locals[inst.localIndex] = localValue{result}
|
||||
mem.instructions = append(mem.instructions, result)
|
||||
return nil
|
||||
}
|
||||
|
||||
func intPredicateString(predicate llvm.IntPredicate) string {
|
||||
switch predicate {
|
||||
case llvm.IntEQ:
|
||||
return "eq"
|
||||
case llvm.IntNE:
|
||||
return "ne"
|
||||
case llvm.IntUGT:
|
||||
return "ugt"
|
||||
case llvm.IntUGE:
|
||||
return "uge"
|
||||
case llvm.IntULT:
|
||||
return "ult"
|
||||
case llvm.IntULE:
|
||||
return "ule"
|
||||
case llvm.IntSGT:
|
||||
return "sgt"
|
||||
case llvm.IntSGE:
|
||||
return "sge"
|
||||
case llvm.IntSLT:
|
||||
return "slt"
|
||||
case llvm.IntSLE:
|
||||
return "sle"
|
||||
default:
|
||||
return "cmp?"
|
||||
}
|
||||
}
|
||||
+1430
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user